4
votes

I have the following requirements for my C# Web API 2 Service:

The service authenticates Users by the combination of Email and a temporary Passcode that gets sent to their inbox, as a factor of authentication. I need to blend in this authentication mechanism with producing OAuth bearer tokens to secure the service and use standard ASP.NET Authorisation mechanism to check each request against the token via some kind of [Authorize] attribute.

I have successfully implemented these steps

  1. User requests Passcode
  2. System generates and emails Passcode to User with 30 days expiration
  3. User authenticates with Email + Passcode
  4. System checks validity of Passcode

But I am not sure how to begin implementing the remaining steps

  1. If Passcode valid, system generates OAuth bearer token
  2. OAuth bearer token lasts as long as Passcode expiration date
  3. Use ASP.NET Identity authorization attributes to perform authentication and authorisation checks
  4. Use OWIN Security and OAuth Middleware to create token
  5. Use claims based authorisation and serialise claims into token

The cited process only describes using ASP.NET Identity Individual User accounts as a means to authenticate which is not how I want to authenticate.

http://www.asp.net/web-api/overview/security/individual-accounts-in-web-api

I actually need to authenticate by checking Email and Passcode.

2

2 Answers

4
votes

I worked in a similar scenario and had implemented an authentication filter (IAuthenticationFilter) and a customized class inherited from OAuthAuthorizationServerProvider. In my case, I needed to authenticate the request with OAuth and a legacy token. I believe that in your case, you will need customize the AuthenticationFilter. See below an example of the AuthenticationFilter:

public class MyAuthenticationFilter : IAuthenticationFilter
{
    private readonly string _authenticationType;

    /// <summary>Initializes a new instance of the <see cref="HostAuthenticationFilter"/> class.</summary>
    /// <param name="authenticationType">The authentication type of the OWIN middleware to use.</param>
    public MyAuthenticationFilter(string authenticationType)
    {
        if (authenticationType == null)
        {
            throw new ArgumentNullException("authenticationType");
        }

        _authenticationType = authenticationType;
    }

    /// <summary>Gets the authentication type of the OWIN middleware to use.</summary>
    public string AuthenticationType
    {
        get { return _authenticationType; }
    }

    /// <inheritdoc />
    public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }

        HttpRequestMessage request = context.Request;

        if (request == null)
        {
            throw new InvalidOperationException("Request mut not be null");
        }


        //In my case, i need try autenticate the request with BEARER token (Oauth)
        IAuthenticationManager authenticationManager = GetAuthenticationManagerOrThrow(request);

        cancellationToken.ThrowIfCancellationRequested();
        AuthenticateResult result = await authenticationManager.AuthenticateAsync(_authenticationType);
        ClaimsIdentity identity = null;

        if (result != null)
        {
            identity = result.Identity;

            if (identity != null)
            {
                context.Principal = new ClaimsPrincipal(identity);
            }
        }
        else
        {
            //If havent success with oauth authentication, I need locate the legacy token
//If dont exists the legacy token, set error (will generate http 401)
            if (!request.Headers.Contains("legacy-token-header"))
                context.ErrorResult = new AuthenticationFailureResult(Resources.SAUTH_ERROR_LEGACYTOKENNOTFOUND, request);
            else
            {
                try
                {
                    var queryString = request.GetQueryNameValuePairs();
                    if (!queryString.Any(x => x.Key == "l"))
                        context.ErrorResult = new AuthenticationFailureResult(Resources.SAUTH_ERROR_USERTYPENOTFOUND, request);
                    else
                    {
                        var userType = queryString.First(x => x.Key == "l").Value;
                        String token = HttpUtility.UrlDecode(request.Headers.GetValues("tk").First());

                        identity = TokenLegacy.ValidateToken(token, userType);
                        identity.AddClaims(userType, (OwinRequest) ((OwinContext)context.Request.Properties["MS_OwinContext"]).Request);
                        if (identity != null)
                        {
                            context.Principal = new ClaimsPrincipal(identity);
                        }
                    }

                }
                catch (Exception e)
                {
                    context.ErrorResult = new AuthenticationFailureResult(e.Message, request);
                }
            }
        }
    }


    /// <inheritdoc />
    public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }

        HttpRequestMessage request = context.Request;

        if (request == null)
        {
            throw new InvalidOperationException("Request mut not be null");
        }

        IAuthenticationManager authenticationManager = GetAuthenticationManagerOrThrow(request);

        // Control the challenges that OWIN middleware adds later.
        authenticationManager.AuthenticationResponseChallenge = AddChallengeAuthenticationType(
            authenticationManager.AuthenticationResponseChallenge, _authenticationType);

        return TaskHelpers.Completed();
    }

    /// <inheritdoc />
    public bool AllowMultiple
    {
        get { return true; }
    }

    private static AuthenticationResponseChallenge AddChallengeAuthenticationType(
        AuthenticationResponseChallenge challenge, string authenticationType)
    {
        Contract.Assert(authenticationType != null);

        List<string> authenticationTypes = new List<string>();
        AuthenticationProperties properties;

        if (challenge != null)
        {
            string[] currentAuthenticationTypes = challenge.AuthenticationTypes;

            if (currentAuthenticationTypes != null)
            {
                authenticationTypes.AddRange(currentAuthenticationTypes);
            }

            properties = challenge.Properties;
        }
        else
        {
            properties = new AuthenticationProperties();
        }

        authenticationTypes.Add(authenticationType);

        return new AuthenticationResponseChallenge(authenticationTypes.ToArray(), properties);
    }

    private static IAuthenticationManager GetAuthenticationManagerOrThrow(HttpRequestMessage request)
    {
        Contract.Assert(request != null);

        var owinCtx = request.GetOwinContext();
        IAuthenticationManager authenticationManager = owinCtx != null ? owinCtx.Authentication : null;

        if (authenticationManager == null)
        {
            throw new InvalidOperationException("IAuthenticationManagerNotAvailable");
        }

        return authenticationManager;
    }
}

In WebApiConfig.cs, you need the add the authentication filter like this:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        // Web API configuration and services
        // Configure Web API to use only bearer token authentication.
        config.SuppressDefaultHostAuthentication();

        config.Filters.Add(new MyAuthenticationFilter(OAuthDefaults.AuthenticationType));
     }
}

I recommend reading the official WEB API poster:

https://www.asp.net/media/4071077/aspnet-web-api-poster.pdf

0
votes

Please see this post for a detailed explanation on how to setup Authentication in asp.net Web API. This should give you a good idea on how authentication with your requirements can be achieved in Web API. Please let me know if you have any questions or issues.

Thank you, Soma.