17

I am following this article for revoking user access :

http://bitoftech.net/2014/07/16/enable-oauth-refresh-tokens-angularjs-app-using-asp-net-web-api-2-owin/

Now consider after validating user I have issued an accesstoken with 30 minutes life span as shown in above article and with refresh token as 1 day but what if admin delete that user in 10 minutes with 20 minutes still left so now in this case I need to revoke access that user.

In order to do this I need to remove that user entry from refresh token table to disallow further access token request but as accesstoken expire time is still having 20 minutes so user would be able to access protected resource which is completely wrong.

So I was thinking to implement caching mechanism to cache the access token on server and also save in database. So when that user is revoked I can simply remove that user entry from cache and database to stop that user access from accessing protected resource.

But this 2 below answers are saying this is not how oauth2 was designed :

Revoke access token of OAuthBearerAuthentication

OAuth2 - unnecessary complexity with refresh token

So my question is :

1) Why caching access token is not considered better than refresh token mechanism and also a bad approach?

My second question is based on this below answer given by @Hans Z. in which he is saying that :

This necessarily would involve the Resource Server (RS) consulting the Authorization Server (AS) which is a huge overhead.

2) In case of revoking access for a user why does RS would consult AS because AS is just for authenticating user and generating access token as per this Article?

3) In the article there are only 2 projects :

  • Authentication.api - Authenticating user and generating access token
  • Resource server - validating accesstoken with the help of [Authorize] attribute

    In above case which is the authorisation server then?

Update : I have decided to use refresh token to revoke user access in case user is deleted and also when user logout i will refresh token from refresh token table because of your requirement that we want to logout user immediately as soon as user clicks on logout.

But here the problem is i have 250 roles associated with user so if i put roles in accesstoken then size of accesstoken will be so huge and we cannot pass such huge accesstoken from header but i cannot query roles for validating user access for endpoints each time that endpoint is called.

So this is 1 another problem which i am facing.

I Love Stackoverflow
  • 6,738
  • 20
  • 97
  • 216
  • Cache token where? There can be thousand of servers that are using token which is issued by one autorization server. – Evk Nov 21 '17 at 12:23
  • @Evk :I have updated my question so basically cache accesstoken on server.I will cache accesstoken on server for each of the user of any client application. – I Love Stackoverflow Nov 21 '17 at 12:28
  • Yes I undestand that on server, but on which one? There can be a lot of resource services which use tokens, each of them on different physical server. And then there is authentication service which is also on different physical server. – Evk Nov 21 '17 at 12:35
  • @Evk I guess cache will be shared between Authentication.api and resourceserver.api project because accesstoken is generated in Authentication.api project and accesstoken will be validated from cache inside resource api server.so cache need to be shared between this 2 project.Does this make sense? – I Love Stackoverflow Nov 21 '17 at 13:26
  • 1
    Having such cache is the same as requiring resource server to call authorization server to validate token( because if that's distributed cache - network call is required to get your token from cache), and that is what we are trying to avoid. We have token, it contains all information necessary to authorize user request without consulting with authorization server in any way. Invalidating access token is not possible without this (contacting authorization server) - that's why refresh tokens exist. Having access token alive for some time after invalidation of refresh one is considered fine. – Evk Nov 21 '17 at 15:11
  • @Evk Appreciate your input but i am just not getting what do you mean by the Authorisation server and what does this authorisation server resides in the link which i am following.Can you please point out that as because i need to understand this mechanism – I Love Stackoverflow Nov 22 '17 at 06:36
  • The thing which generates the token. – Evk Nov 22 '17 at 06:38
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/159547/discussion-between-learning-overthinker-confused-and-evk). – I Love Stackoverflow Nov 22 '17 at 07:02
  • Why not just remove both the access tokens and the refresh tokens from the database so that the user is completely revoked? (if you are checking against the database on each request) – Soniku Nov 29 '17 at 18:19
  • As per experience, caching is not bad at all, not only for access token but for other scenarios. It totally depends upon your cache management into your application. Caching is awesome to save your resource consumption, if managed correctly. You have to validate & invalidate cache for all scenarios of your application. But, if you managed it incorrectly & missed any scenario, then it would be nightmare for you to figure the issue. I am using Redis as cache for authorization flows for one of the product & saving 90% of hits to database, even after role/permission updates in RBAC system. – Amreesh Tyagi Jun 04 '19 at 12:58

4 Answers4

18

There seems to be 2 different questions here: about access token and about big list of roles.

Access Token

OAuth2 was designed to be able to handle high load and this requires some trade-offs. Particularly this is the reason why OAuth2 explicitly separates "Resource Server" and "Authorization Server" roles on the one hand, and "access token" and "refresh token" on the other hand. If for each request you have to check user authorization, it means that your Authorization Server should be able to handle all requests in your system. For high load systems this is not feasible.

OAuth2 allows you to make the following trade-off between performance and security: Authorization Server generates an Access Token that can be verified by the Resource Server without accessing Authorization Server (either at all or at least not more than once for a life of Authorization Server). This is effectively caching of the authorization information. So in this way you can drastically reduce load on your Authorization Server. The drawback is again the same as always with caching: authorization information might get stall. By varying access token life time you can tune performance vs. security balance.

This approach also might help if you do micro-services architecture where each service has own storage and don't access each other's.

Still if you don't have much load and you only have single Resource Server rather than tons of different services implemented using different technologies, there is nothing that prohibits you from actually doing full-blown validation on every request. I.e. yes, you may store Access Token in the DB, verify it on every access to the Resource Server and remove all Access Tokens when user is deleted, etc. But as @Evk noticed, if this is your scenario - OAuth2 is an overshoot for you.

Big list of roles

AFAIU OAuth2 doesn't provide an explicit feature for user roles. There is "Scopes" feature that might be also used for roles and its typical implementation it will produce too long string for 250 roles. Still OAuth2 doesn't explicitly specify any particular format for access token, so you can create a custom token that will hold roles information as a bit mask. Using base-64 encoding you can get 6 roles into a single character (64 = 2^6). So 250-300 roles will be manageable 40-50 chars.

JWT

Since you'll probably need some custom token anyway, you might be interested in the JSON Web Tokens aka JWT. In short JWT lets you specify custom additional payload (Private claims) and put your roles bitmask there.

You may actually use JWT alone without whole OAuth2 stuff if you don't really need any of the OAuth2 advanced features (such as scopes). Although JWT-tokens are supposed to be validated just by theis contents only, you may still store them in your local DB and do additional validation against the DB (as you were going to do with access refresh token).


Update Dec 1, 2017

If you want to use OWIN OAuth infrastructure, you can customize token format providing custom formatter via AccessTokenFormat in OAuthBearerAuthenticationOptions and OAuthAuthorizationServerOptions. You may also override RefreshTokenFormat.

Here is a sketch that shows how you can "compress" roles claims into a single bitmask:

  1. Define your CustomRoles enumeration that list all roles you have
[Flags]
public enum CustomRoles
{
    Role1,
    Role2,
    Role3,

    MaxRole // fake, for convenience
}
  1. Create EncodeRoles and DecodeRoles methods to convert between IEnumerable<string> format for roles and base64-encoded bit mask based on CustomRoles defined above such as:
    public static string EncodeRoles(IEnumerable<string> roles)
    {
        byte[] bitMask = new byte[(int)CustomRoles.MaxRole];
        foreach (var role in roles)
        {
            CustomRoles roleIndex = (CustomRoles)Enum.Parse(typeof(CustomRoles), role);
            var byteIndex = ((int)roleIndex) / 8;
            var bitIndex = ((int)roleIndex) % 8;
            bitMask[byteIndex] |= (byte)(1 << bitIndex);
        }
        return Convert.ToBase64String(bitMask);
    }

    public static IEnumerable<string> DecodeRoles(string encoded)
    {
        byte[] bitMask = Convert.FromBase64String(encoded);

        var values = Enum.GetValues(typeof(CustomRoles)).Cast<CustomRoles>().Where(r => r != CustomRoles.MaxRole);

        var roles = new List<string>();
        foreach (var roleIndex in values)
        {
            var byteIndex = ((int)roleIndex) / 8;
            var bitIndex = ((int)roleIndex) % 8;
            if ((byteIndex < bitMask.Length) && (0 != (bitMask[byteIndex] & (1 << bitIndex))))
            {
                roles.Add(Enum.GetName(typeof(CustomRoles), roleIndex));
            }
        }

        return roles;
    }
  1. Use those methods in a custom implementation of SecureDataFormat<AuthenticationTicket>. For simplicity in this sketch I delegate most of work to standard OWIN components and just implement my CustomTicketSerializer that creates another AuthenticationTicket and uses standard DataSerializers.Ticket. This is obviously not the most efficient way but it shows what you could do:
public class CustomTicketSerializer : IDataSerializer<AuthenticationTicket>
{

    public const string RoleBitMaskType = "RoleBitMask";
    private readonly IDataSerializer<AuthenticationTicket> _standardSerializers = DataSerializers.Ticket;

    public static SecureDataFormat<AuthenticationTicket> CreateCustomTicketFormat(IAppBuilder app)
    {
        var tokenProtector = app.CreateDataProtector(typeof(OAuthAuthorizationServerMiddleware).Namespace, "Access_Token", "v1");
        var customTokenFormat = new SecureDataFormat<AuthenticationTicket>(new CustomTicketSerializer(), tokenProtector, TextEncodings.Base64Url);
        return customTokenFormat;
    }

    public byte[] Serialize(AuthenticationTicket ticket)
    {
        var identity = ticket.Identity;
        var otherClaims = identity.Claims.Where(c => c.Type != identity.RoleClaimType);
        var roleClaims = identity.Claims.Where(c => c.Type == identity.RoleClaimType);
        var encodedRoleClaim = new Claim(RoleBitMaskType, EncodeRoles(roleClaims.Select(rc => rc.Value)));
        var modifiedClaims = otherClaims.Concat(new Claim[] { encodedRoleClaim });
        ClaimsIdentity modifiedIdentity = new ClaimsIdentity(modifiedClaims, identity.AuthenticationType, identity.NameClaimType, identity.RoleClaimType);
        var modifiedTicket = new AuthenticationTicket(modifiedIdentity, ticket.Properties);
        return _standardSerializers.Serialize(modifiedTicket);
    }

    public AuthenticationTicket Deserialize(byte[] data)
    {
        var ticket = _standardSerializers.Deserialize(data);
        var identity = ticket.Identity;
        var otherClaims = identity.Claims.Where(c => c.Type != RoleBitMaskType);
        var encodedRoleClaim = identity.Claims.SingleOrDefault(c => c.Type == RoleBitMaskType);
        if (encodedRoleClaim == null)
            return ticket;

        var roleClaims = DecodeRoles(encodedRoleClaim.Value).Select(r => new Claim(identity.RoleClaimType, r));
        var modifiedClaims = otherClaims.Concat(roleClaims);
        var modifiedIdentity = new ClaimsIdentity(modifiedClaims, identity.AuthenticationType, identity.NameClaimType, identity.RoleClaimType);
        return new AuthenticationTicket(modifiedIdentity, ticket.Properties);
    }
}
  1. In your Startup.cs configure OWIN to use your custom format such as:
var customTicketFormat = CustomTicketSerializer.CreateCustomTicketFormat(app);
OAuthBearerOptions.AccessTokenFormat = customTicketFormat;
OAuthServerOptions.AccessTokenFormat = customTicketFormat;
  1. In your OAuthAuthorizationServerProvider add ClaimTypes.Role to the ClaimsIdentity for each role assigned to the user.

  2. In your controller use standard AuthorizeAttribute such as

    [Authorize(Roles = "Role1")]
    [Route("")]
    public IHttpActionResult Get()
    

For convenience and some safety you may subclass AuthorizeAttribute class to accept CustomRoles enum instead of string as role configuration.

SergGr
  • 23,570
  • 2
  • 30
  • 51
  • Upvoted for your kind efforts towards helping me and thank you so much for the answer but i am unable to understand role part.You said i need to create custom token but then how owin will validate that token as because token validation is done based on machine key as far as i know so if i am creating my own custom token then how owin will validate that token? – I Love Stackoverflow Nov 24 '17 at 06:33
  • @Learning-Overthinker-Confused, I've added some code to show how you can "hack in" a bitmask into the standard OWIN OAuth infrastructure. – SergGr Dec 01 '17 at 05:13
  • I have 1 question related to authentication and authorization and that is whether to use Identity server 4 or Taiseer Joudeh author code.Can you please suggest me ? – I Love Stackoverflow Dec 21 '17 at 10:09
  • Hi @SergGr, I am having the same issue. I have to automatically log in to the API provider's site to generate a request token, which will be passed along with the client ID, client secret to generate the access token. The request token is valid for some time, but can only be used once. The access token will be passed as a part of the Bearer token and that is valid for a relatively short period of time only. For local testing, I have automated the whole process. But I am wondering what will happen in the case of high loads as the API provider is having a rate limiter (which is not that high) – Anshuman Kumar Jun 28 '22 at 12:57
  • Any suggestions to cache the access token ( I am thinking Redis for this ) – Anshuman Kumar Jun 28 '22 at 12:59
0

I hope I got your questions right and can provide some answers:

1) You can cash it if you developed your AS to require validating it each time the user login.

2) I think @Hans Z. means revoking user by the AS. When RS revoke the user it doesn't change the fact that they are still the ones identified by AS. But when AS revoke the user, it prevent their use of their identity.

3) The article probably assumes that authorization is done by the RS, AS is only responsible to tell you who is the users, and the RS is to decide authorization based on that.

Majid ALSarra
  • 394
  • 2
  • 7
0

Main advantage of refresh token approach is to reduce number to database queries, the access token has the claims and signed so the token can be trusted without querying the database.

Caching access token will work but then you will have to query the cache on each request.

It's a trade off you have to choose between, n minutes of delay in access permission changes vs. number of queries to check access token validity

With added complexity you can almost achieve both in that case you will have to store the cache in server RAM, and stored only revoked tokens to keep the list small. The complexity comes when you have multiple instances of servers you will have to keep this cache of revoked tokens in sync between your RSs and AS.

Basically when the access token is revoked the AS will have to inform all the RSs to add that access token to revoked token cache.

Whenever there is a resource request RS will check if the token is revoked or not, if not revoked RS servers the resource. This way the overhead is there on each request but it is highly reduced as the cache is in the memory and number of revoked tokens will be very less compared to number of valid tokens.

Bhoju
  • 71
  • 4
0

"The authorization server may be the same server as the resource server or a separate entity." [RFC 6749, p. 6]

With that said, you could cache if that was the case, but the token should be understandable by the resource server, and shouldn't need to be cached. If that's a detail of your implementation, so yes caching is possible, but not necessary.