Search code examples
c#mockingintegration-testingidentityserver4

MockIdentityServer Client Claims not included in the access token when Grant type was ClientCredentialsFlow


I have API Controller which is protected under the policy. This policy is configured in the Startup.cs like

 options.AddPolicy("InternalClient", policy =>
                policy.RequireAssertion(context =>
                    context.User.HasClaim(c =>                           
                         (c.Type == "client_id" && c.Value == "installation-logic-client-id"))));

while the controller method is :

    [HttpGet("{familyId}/versions/{version}/infos")]
    [Authorize(Policy = "InternalClient")]     
    public IActionResult GetTestInfo(Guid testFamilyId, string version)
    {
        ..............................
    }

and testing the above method, I am getting tokens from MockIdentityServer. There I am configuring client like

       yield return new Client
        {
            ClientId = "installation-logic-client-id",
            ClientSecrets = new[] {new Secret("installation-logic-client-secret".Sha256())},
            AllowedGrantTypes = GrantTypes.ClientCredentials,
            AllowedScopes = new[] {"installation-logic-scope"},
            AllowOfflineAccess = true,
            AccessTokenType = AccessTokenType.Jwt,
            RefreshTokenUsage = TokenUsage.OneTimeOnly,
            RefreshTokenExpiration = TokenExpiration.Sliding,

            Claims = new List<Claim>() // I want these claims to  be added in the access_token so that they can be verified while making the request.
            {
                new Claim("client_id", "installation-logic-client-id")
            },
            AlwaysSendClientClaims = true,
            AlwaysIncludeUserClaimsInIdToken = true,
        };

I always get token successfully, But unfortunately, that token doesn't contain claims information which I am testing and for which policy is being setup. Following is the call..

            private async Task<string> GetTokenForInternalClient()
            {
               var tokenRequest = new ClientCredentialsTokenRequest() 
               {
                  Address = await GetTokenEndpoint(),
                  ClientId = MockConstants.TokenInstallationLogicClientId,
                  ClientSecret = MockConstants.TokenInstallationLogicClientSecret,
                  Scope = MockConstants.TokenInstallationLogicScope
               };

             var tokenResponse = await 
             _identityServerClient.RequestClientCredentialsTokenAsync(tokenRequest);
              if (tokenResponse.IsError) throw new MockIdentityServerException(tokenResponse);

             return tokenResponse.AccessToken; // Here I see very short token. Clearly it doesn't contains the claims.
           }

At the moment, I am getting 'Unauthorized' request. Because it doesn't pass the policy due to the unavailability of claims. Can any body tell me what wrong am I doing? Is there is specific way to get access_token with all claims

After changing client_id to id at policy level within Client object, also changed the TokenType as 'Jwt' instead opf Reference, I got following Payload.

    {
  "nbf": 1574191641,
  "exp": 1574195241,
  "iss": "http://localhost:5000",
  "aud": "installation-logic-scope",
  "client_id": "installation-logic-client-id",
  "scope": [
    "installation-logic-scope"
  ]
}

Updated Claims ( Payload Body )

{
  "nbf": 1574198669,
  "exp": 1574202269,
  "iss": "http://localhost:5000",
  "aud": "installation-logic-client-id",
  "client_id": "installation-logic-client-id",
  "scope": [
    "installation-logic-scope"
  ]
}

Startup.cs

private static void ConfigureAuthorization(IServiceCollection services)
    {
        services.AddAuthorization(options =>
        {
            options.AddPolicy("admin", pb => pb.RequireClaim("Role", "admin", "orgadmin"));
            options.AddPolicy("InternalClient", policy =>
                policy.RequireAssertion(context =>
                    context.User.HasClaim(c =>
                        ((c.Type == "Role" && (c.Value == "admin" || c.Value == "orgadmin")) ||
                         (c.Type == "id" && c.Value == "installation-logic-client-id")))));
        });
    }

    private void ConfigureDbContexts(IServiceCollection services)
    {
             ........................            
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    [UsedImplicitly]
    public void Configure(IApplicationBuilder app, PackageHandlingContext dbContext)
    {           
        // middlewares: order is important
        app.UseRouting();
        app.UseCors("AnyOrigin");
        app.UseAuthentication();
        app.UseAuthorization();
        app.UseHttpsRedirection();

        app.UseEndpoints(endpoints => endpoints.MapControllers());

        dbContext.Database.EnsureCreated();
    }

Solution

  • I figured out all the problems in this whole story.

    First of all while building up in memoy ApiResources, scopes should also contain "client_id".

    Scopes = new List<Scope>
    {
       new Scope(MockConstants.PackageHandlingScope, new[] {"Role", "client_id"})
    },
    

    Secondly, building up Test Client, only "id" as claim type needed to used because client_ is already prefixed with claim type.

    Updated Client would be

    yield return new Client
            {
                ClientId = MockConstants.TokenInstallationLogicClientId,
                ClientSecrets = new[] {new Secret(MockConstants.TokenInstallationLogicClientSecret.Sha256())},
                AllowedGrantTypes = GrantTypes.ClientCredentials,
                AllowedScopes = new[] {MockConstants.PackageHandlingScope},
                AllowOfflineAccess = true,
                AccessTokenType = AccessTokenType.Jwt,
                RefreshTokenUsage = TokenUsage.OneTimeOnly,
                RefreshTokenExpiration = TokenExpiration.Sliding,
    
                Claims = new List<Claim>
                {
                    new Claim("id", "installation-logic-client-id")
                }
            };
    

    Rest of the things are fine.

    @Ruard van Elburg really thanks to your comments, you pointed out "id" stuff which was necassary while building up the test client. Because Client claims alwazs get prefixed while transfering over the wire to application.

    Thanks