Search code examples
architectureidentityserver4

Right architecture for Authentication and Authorization with IdentityServer4


I'm trying to implement the right architecture with authentication on IdentityServer4 (IS4). So I will have a server that does as identity provider with oidc and oauth2 tokens for SSO and access tokens to protect the web api. User profiles will be stored in the IS4 database. I have many applications that will refer to the IS4, but let's think about just one application for an easy scenario.

I read a few article about the difference between authentication and authorization, and if I correctly understood, it's not a good idea to store authorization info in claims or with other tricks in IS4. It should manage only the identity of the user and it's attributes but NOT the permissions it has in other applications.

So, my doubt is about the management of permissions... about authorization. Does every application know all the users, matching by id to the IS4 representation, to manage the specific permission? This means that I have to sync every application DB with the IS4 DB! Is it preferable to implement a service for authorization management that stores the rules that are not retrievable from the claims?

This is an example of the problem:

  • The user John is a "standard user". I see it in the claims. I can have the information about his generic role.
  • Because John is not an "administrator" he cannot access the print and setup menus in the application.
  • I would like to dynamically add the authorization to access the print menu to john, but NOT the setup menu.

Since John will maintain the role of "standard user" I think I must store the info of "show print menu" permission in the specific application.

Is my vision of the architecture correct or is there a better way to implement the scenario?


Solution

  • I would put claims that are truly global in IdentityServer, like name, email and then put application specific claims in each application. just to follow the separation of concerns. It will otherwise be pretty messy if you put all the claims in IdentityServer, better to follow separation of concerns.

    You do not need to develop some custom sync between the databases, instead you create a local entry in each application when it encounters a new user.

    Adding local claims during the login in each application is easy to do using claims transformation, like this example:

    public class BonusLevelClaimTransformation : IClaimsTransformation
    {
        public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
        {
            if (!principal.HasClaim(c => c.Type == "bonuslevel"))
            {
                //Lookup bonus level.....
                principal.Identities.First().AddClaim(new Claim("bonuslevel", "12345"));
            }
            return Task.FromResult(principal);
        }
    }
    

    You should try to reduce the claims in the tokens, because they can otherwise grow quite big. Also the more claims you add to the local ClaimsPrincipal object, the larger the session cookies becomes.

    When the session cookies grows, then ASP.NET Core will spit it up into multiple cookies, like: enter image description here

    You want to keep your tokens small and not every claim needs to be in the token to fulfill the basic authorization and authentication needs. A claim like "CanPrint", "Address" or "UserWebPage" should probably not be in the token it self. One possibility is to load the not-so-frequently-used claims on a need basis by asking the UserInfo endpoint (documentation).

    You also have the GetClaimsFromUserInfoEndpoint option that you can use to your advantage. See this article for more details.

    When you login at IdentityServer, you give consent to the scopes you have asked for. Then in the client application that receives back the ID-token, it will then take some of those claims and put them in the session cookie. Then the ID-token is typically dropped. Additional information can then be requested by individual pages (from the UserInfo endpoint), that needs additional data than the claims found in the session cookie. For example the "UserWebPage" is probably only needed on a few pages and the "CanPrint" on some other pages. Alternatively you download and cache it "somehow" during the session. But it's up to you how you do this. You also do want to to keep the session cookie small, because it is included in every request.