4
\$\begingroup\$

I intend to expose a ASP.Net REST API for a project. I would like to use the default ASP.Net Identity system to provide the SQL backend. Users are managed in a different project. For my REST API project I simply point the connectionstring to the same database.

I want to be able to authorize/get the ClaimsIdentity on a per-request basis; if I revoke a user's claim for example I want the next request to reflect that change. When using bearer tokens the claims are stored in the token and will not reflect the change until a logout occured or the token expired. It also turns out that the Identity system doesn't provide facilities to revoke tokens which would make things easy(er). I could add a custom claim (let's call it "sessionid" with a Guid or something) and using an AuthorizationFilterAttribute's OnAuthorize method I could check a table that holds "sessionid's" (the GUID's) and a reference to the user. Whenever a change in claims/roles/whatever is made I could simply trash all GUID's from that table that are referencing that specific user. However, that would require my other project to 'know' about this construction because it'll have to take care of this "invalidating sessions" which is what I would like to avoid.

I came up with the following solution and would like to know if this makes sense to anyone:

My UserManager<IdentityUser> assigns a MyClaimsIdentityFactory<IdentityUser> to it's ClaimsIdentityFactory property in it's constructor. My MyClaimsIdentityFactory<IdentityUser> returns a ClaimsIdentity with only a UserId in it (the user's PK (GUID) from the AspNetUsers table) which is returned with a 'decent' expiry of (say) 6 months.

Next, I implemented a AuthorizeUserManagerAttribute (need to come up with a better name...) by inheriting from AuthorizationFilterAttribute. It's OnAuthorizationAsync method retrieves the ClaimsIdentity from actionContext.RequestContext.Principal.Identity and then uses the UserId stored in the identity to retrieve the user from the database, using the ApplicationUserManager's FindByIdAsync method and sets the actionContext.RequestContext.Principal to a newly created object.

Because the code may explain it better; below is my implementation (which may need a little polishing):

    public override async Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
    {
        var claimsIdentity = actionContext.RequestContext.Principal.Identity as ClaimsIdentity;
        if (claimsIdentity == null)
        {
            actionContext.Response = this.GetForbiddenResult();
        }
        else
        {
            var userIdClaim = claimsIdentity.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
            if (userIdClaim != null && claimsIdentity.IsAuthenticated)
            {
                var userId = userIdClaim.Value;
                using (var um = actionContext.Request.GetOwinContext().Get<ApplicationUserManager>())
                {
                    var user = await um.FindByIdAsync(userId);
                    //More actual code:
                    //var user = await MyCache.GetFromCache(MyCache.CreateKey("user", userId), () => um.FindByIdAsync(userId), TimeSpan.FromMinutes(1));
                    if (user != null)
                    {
                        var id = new ClaimsIdentity(claimsIdentity.AuthenticationType, ClaimTypes.Name, null);

                        id.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id.ToString(), ClaimValueTypes.String));
                        id.AddClaim(new Claim(ClaimTypes.Name, user.UserName, ClaimValueTypes.String));
                        id.AddClaim(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", ApplicationUserManager.IdentityProviderName, ClaimValueTypes.String));
                        id.AddClaims(user.Claims.Select(c => new Claim(c.ClaimType, c.ClaimValue)));
                        actionContext.RequestContext.Principal = new ClaimsPrincipal(id);
                    }
                    else
                    {
                        actionContext.Response = this.GetForbiddenResult();
                    }
                }
                await base.OnAuthorizationAsync(actionContext, cancellationToken);
            }
            else
            {
                actionContext.Response = this.GetForbiddenResult();
            }
        }
    }

    public HttpResponseMessage GetForbiddenResult()
    {
        return new HttpResponseMessage(HttpStatusCode.Forbidden);
    }
}

This, in effect, ensures that for each request made against my API the latest information about the user is retrieved from the database. Any changes made to the user (revoked/added claims, revoked/added roles etc.) will be reflected immediately.

I understand that hitting the DB upon each request will take it's toll; I could possibly add a little bit of caching to cache the result for, say, a minute or so so that changes will be reflected at most a minute later and not hit the DB 50 times when some program calls the API 50 times in a minute.

So my question is: Is there a better way to make sure changes to a user's claims/roles are reflected (near) real-time? And as an 'aside': am I abusing the authenticaion/authorization system?

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.