Search code examples
c#oauth-2.0asp.net-web-api2asp.net-identity-2

Web API2 identity2 bearer token permission change


Using Owin + Oauth2 + Identity2.

I have a web Api with default basic authentication setup that i have modified.

my startup.cs partial class

public void ConfigureAuth(IAppBuilder app)
    {
        // Enable the application to use a cookie to store information for the signed in user
        // and to use a cookie to temporarily store information about a user logging in with a third party login provider
        app.UseCookieAuthentication(new CookieAuthenticationOptions());
        app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);//TODO: prob wont need this

        // Configure the application for OAuth based flow
        PublicClientId = "self";

        OAuthOptions = new OAuthAuthorizationServerOptions
        {
            TokenEndpointPath = new PathString("/Token"),
            Provider = new ApplicationOAuthProvider(PublicClientId),
            AuthorizeEndpointPath = new PathString("/api/Account/ExternalLogin"),//TODO: prob wont need this
            AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
            // In production mode set AllowInsecureHttp = false
            AllowInsecureHttp = true //TODO: set debug mode
        };

        // Token Generation
        app.UseOAuthBearerTokens(OAuthOptions);
    }

my startup.cs class partial at the root

public void Configuration(IAppBuilder app)
    {
        HttpConfiguration config = new HttpConfiguration();

        ConfigureAuth(app);

        WebApiConfig.Register(config);
        app.UseCors(Microsoft.Owin.Cors.CorsOptions.AllowAll);
        app.UseWebApi(config);
    }

my applicationOAuthProvider.cs

public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
    {
        //get user
        var service = new CarrierApi.CarrierManagementClient();
        var result = service.LoginAsync(context.UserName, context.Password);
        var user = result.Result.Identity;

        //TODO: log stuff here? i.e lastlogged etc?

        if (user == null)
        {
            context.SetError("invalid_grant", "The user name or password is incorrect.");
            return;
        }

        ClaimsIdentity oAuthIdentity = user;
        ClaimsIdentity cookiesIdentity = user;

        AuthenticationProperties properties = CreateProperties(user.GetUserName());
        AuthenticationTicket ticket = new AuthenticationTicket(oAuthIdentity, properties);
        context.Validated(ticket);
        context.Request.Context.Authentication.SignIn(cookiesIdentity);
    }

As you can see i actually go and get the identity via a wcf call to our existing DB. when using postman, i get the /token url and obtain my bearer token, on the next request i pass it into the header and call my controller method.

[Authorize(Roles = "Templates_Access")]
    public string Post([FromBody]string value)
    {
        return "woo";
    }

This works great, if the user has the permission it wont allow access, if they do it does.

However if i go to our website that uses the same wcf and DB and change a users permission, if i send the same request on postman it still allows access even though ive removed that permission on the role the user is assigned too.

How do i make sure that permissions are "refreshed" or checked again on each request?


Solution

  • Every roles of a user logged in are stored in the bearer token at login time as claim, in the GrantResourceOwnerCredentials method. If a request have to be authorized, the role is searched on the list stored in the bearer token by the default implementation of AuthorizationFilter; so if you change the user's permissions, you need a new login.

    This behavior respects the Stateless contraint of a Restfull architecture, as Fielding wrote in his dissertation, and also this is a good balance between performance and security

    If you need a different behavior there is more than one possibility.

    Refresh Token

    You Can use Refresh Token, implementing the GrantRefreshToken method of applicationOAuthProvider class; you can retrieves the refreshed user's permissions and create a new access token; this is a good article to learn how.

    Keep in mind:

    • more complexity on client
    • no real time effect; you have to wait the access Token expiring
    • If the Access Token has a short life, you have to update it often (even when the user permissions are not changed) otherwise a long life does not solve the problem

    Check Permissions each request

    You can implement a custom AuthorizationFilter and check in the database the permissions of the user, but it is a slow solution.

    Cache and Login Session

    You can generate a user session's key (like a guid) for each login in the GrantResourceOwnerCredentials method, and store it in the bearer token as a claim. You have to store it also in a cache system (like Redis), using two index: the user session's key and the userId. The official documentation of Redis explains how.

    When the permessions of a user are changed, you can invalidate every sessions of that user in the cache system, searching by userId

    You can implement a custom AuthorizationFilter and check for each request in cache if the session is valid, searching by user session's key.

    Be careful: this will violate the stateless constraint and your architecture will not restfull


    Here you can find the standard implementation of AuthorizaAttribute filter. You can create your custom filter extending AuthorizeAttribute and overriding the IsAuthorized method.

    Most likely there are other ways, but how often are changed the permissions of a user? In many systems, also in systems where the security is the first requirement, if the permissions's configuration of a user is changed during an active session, it is necessary a new login to active the new one. Are you sure you needs to modify this standard behavior?

    If you are, I suggest the solution with a cache system.