Search code examples
asp.net-mvcasp.net-web-api2asp.net-identity-2owin-middleware

Dissecting ASP.NET MVC Identity for OAuth Bearer Authentication


I'm learning how to put the Asp.Net MVC Identity 2.0 to work.

I have this code that works for OAuth Bearer

    [HttpGet]
    [ActionName("Authenticate")]
    [AllowAnonymous]
    public String Authenticate(string user, string password)
    {
        if (string.IsNullOrEmpty(user) || string.IsNullOrEmpty(password))
        {
            return "Failed";
        }

        var userIdentity = UserManager.FindAsync(user, password).Result;
        if (userIdentity != null)
        {
            if (User.Identity.IsAuthenticated)
            {
                return "Already authenticated!";
            }

            var identity = new ClaimsIdentity(Startup.OAuthBearerOptions.AuthenticationType);
            identity.AddClaim(new Claim(ClaimTypes.Name, user));
            identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, userIdentity.Id));

            AuthenticationTicket ticket = new AuthenticationTicket(identity, new AuthenticationProperties());
            var currentUtc = new SystemClock().UtcNow;
            ticket.Properties.IssuedUtc = currentUtc;
            ticket.Properties.ExpiresUtc = currentUtc.Add(TimeSpan.FromMinutes(1));

            string AccessToken = Startup.OAuthBearerOptions.AccessTokenFormat.Protect(ticket);
            return AccessToken;
        }
        return "Failed in the end";
    }

Here is the code for Startup.Auth.cs

    //This will used the HTTP header Authorization: "Bearer 1234123412341234asdfasdfasdfasdf"
    OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
    app.UseOAuthBearerAuthentication(OAuthBearerOptions);

I have looked at the source code for ClaimsIdentity and AuthenticationTicket and I don't see how the ticket is registered for the identity.

My question is how did this ticket get registered with the Owin pipeline?

My aim is to revoke this ticket if possible.

Thanks in advance.


Solution

  • First off, here is a great tutorial on ASP.NET Identity 2 by Taiseer Joudeh.

    This is the line that adds Bearer token processing to an OWIN application pipeline.

    app.UseOAuthBearerAuthentication(OAuthBearerOptions);
    

    Also, did you write the authorization provider yourself? My startup code looks more like this:

    app.CreatePerOwinContext(ApplicationDbContext.Create);
    app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
    app.CreatePerOwinContext<ApplicationRoleManager>(ApplicationRoleManager.Create);
    
    PublicClientId = "self";
    OAuthServerOptions = new OAuthAuthorizationServerOptions
    {
        AllowInsecureHttp = true,
        TokenEndpointPath = new PathString("/Token"),
        AccessTokenExpireTimeSpan = TimeSpan.FromMinutes(1440),     //TODO: change to smaller value in production, 15 minutes maybe
        Provider = new SimpleAuthorizationServerProvider(PublicClientId),
        RefreshTokenProvider = new SimpleRefreshTokenProvider()
    };
    
    app.UseOAuthAuthorizationServer(OAuthServerOptions);
    
    OAuthBearerOptions = new OAuthBearerAuthenticationOptions();
    app.UseOAuthBearerAuthentication(OAuthBearerOptions);
    

    My SimpleAuthorizationServerProvider then has a Grant method like this:

    public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin") ?? "*";
    
        context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
    
        var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>();
    
        ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password);
    
        if (user == null)
        {
            context.SetError("invalid_grant", "The user name or password is incorrect.");
            return;
        }
    
        var identity = new ClaimsIdentity(context.Options.AuthenticationType);
        identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()));
        identity.AddClaim(new Claim(ClaimTypes.Name, context.UserName));
        identity.AddClaim(new Claim("sub", context.UserName));
    
        foreach (var role in userManager.GetRoles(user.Id))
        {
            identity.AddClaim(new Claim(ClaimTypes.Role, role));
        }
    
        var props = new AuthenticationProperties(new Dictionary<string, string>
        {
            {"as:client_id", context.ClientId ?? string.Empty}
        });
    
        var ticket = new AuthenticationTicket(identity, props);
        context.Validated(ticket);
    }
    

    Just about all of this was based on the tutorial mentioned above. Hope it helps.

    Update There is no standard way to revoke a token according to Taiseer on this page.

    Revoking access from authenticated users: Once the user obtains long lived access token he’ll be able to access the server resources as long as his access token is not expired, there is no standard way to revoke access tokens unless the Authorization Server implements custom logic which forces you to store generated access token in database and do database checks with each request. But with refresh tokens, a system admin can revoke access by simply deleting the refresh token identifier from the database so once the system requests new access token using the deleted refresh token, the Authorization Server will reject this request because the refresh token is no longer available (we’ll come into this with more details).

    However, here is an interesting approach that may accomplish what you need. It will just take a bit of custom implementation.