Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to authenticate user in Web API 2 when being part of a legacy ASP.NET MVC application?

I have a Web API which is currently used by AngularJS apps in an ASP.NET MVC web application. The MVC application is utilizing ASP.NET Forms Authentication as authentication mechanism. How should I authenticate a user of the Web API when the client is not the web client but e.g. a stand-alone service. What I've done right now is adding a login method to the Web API which gives anyone with right credentials access:

[Route("api/v2/login"), HttpPost]
[AllowAnonymous]
public IHttpActionResult Post([FromBody]Credentials credentials)
{
    var principal = FindPrincipal(credentials);
    if (principal != null)
    {
        FormsAuthentication.SetAuthCookie(principal.Identity.Name, false);
        return Ok();
    }
    return Unauthorized();
}

My question is if this is how this should be solved or if there's a better way?

like image 570
Christian Avatar asked Jun 28 '15 22:06

Christian


1 Answers

You can use the token authentication mechanism for WebApi2.

The flow will be some thing like this:

users send you an https request on:

https://yourApiUrl/Token

content type of request should be:

application/x-www-form-urlencoded

body should include:

grant_type=password&username=yourWebsFormsUsername&password=yourWebFormsPassword

your OWIN startup class will look some thing like this:

public partial class Startup
{
    public static OAuthAuthorizationServerOptions OAuthOptions { get; private set; }

    public static string PublicClientId { get; private set; }

    public void ConfigureAuth(IAppBuilder app)
    {
        app.UseCookieAuthentication(new CookieAuthenticationOptions());
        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
        app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);

        PublicClientId = "self";
        OAuthOptions = new OAuthAuthorizationServerOptions
        {
            TokenEndpointPath = new PathString("/Token"),
            Provider = new YourOAuthProvider(),
            AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
            AllowInsecureHttp = true,
        };

        app.UseOAuthBearerTokens(OAuthOptions);
    }
}

Notice YourOAuthProvider above, thats the important part. This is your custom provider that will validate your username/password against what ever credential storage you have. In your case aspnet_membership table. This validation is done below in method RequestHasValidCredentials:

public class YourOAuthProvider : OAuthAuthorizationServerProvider
{
    public string apikey = string.Empty;

    public override async Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
    {
        context.Validated();
    }

    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {

        if (RequestHasValidCredentials(context.UserName, context.Password))
        {
            var id = new ClaimsIdentity(context.Options.AuthenticationType);
            id.AddClaim(new Claim("username", context.UserName));

            context.Validated(id);
        }
        else
        {
            context.SetError("invalid_grant", "The user name or password is incorrect.");
            return;
        }            
    }
}

the response your user will get to the above will be a token, which will include the username, or any other information you added into the context in above method:

id.AddClaim(new Claim("username", context.UserName));

the reponse from the above token api call will be something like this:

{
    "access_token": "9TIpW2m2rUbB_Bmb7kKAQ9GH4hgfnKF8g3fL0tAre2gcFjI45fajmG6qdOJe-A",
    "token_type": "bearer",
    "expires_in": 1209599
}

your user will then have to pass this token as Http Authorization header for all the API calls. They need to pass this in using Bearer scheme, e.g.:

Bearer 9TIpW2m2rUbB_Bmb7kKAQ9GH4hgfnKF8g3fL0tAre2gcFjI45fajmG6qdOJe-A

as this token contains username, you will be able to know who the user is. Last thing is now to read this token and retrieve the username. For that you need to create a custom Authorize attribute, and decorate your controller or methods with that.

public class YourAuthorizeAttribute : AuthorizationFilterAttribute
{
    public override void OnAuthorization(HttpActionContext actionContext)
    {
        var ticket = Startup.OAuthOptions.AccessTokenFormat.Unprotect(actionContext.Request.Headers.Authorization.Parameter);

        string username = claims.Where(x => x.Type == "username").FirstOrDefault();

        base.OnAuthorization(actionContext);
    }
}

all other custom authorization logic can be added in here, once you have the username.

You can pass in other custom information at the time of generating token, and read it in here. (in case you need that for any other special authorization logic.)

Its lengthy approach, but will work will any credentials storage.

like image 193
M. Ali Iftikhar Avatar answered Oct 27 '22 09:10

M. Ali Iftikhar