2
votes

I have set up IdentityServerV4 with a client using authorization code + PKCE and have set the access token type to reference.

new Client
{
    ClientId = "app",
    ClientName = "My Application",
    AllowedGrantTypes = GrantTypes.Code,
    RequireClientSecret = false,
    RedirectUris = { "http://site.example.com:3000/callback" },
    AllowedCorsOrigins = { "http://site.example.com:3000" },
    AllowedScopes = { "openid", "profile", "email" },
    AccessTokenType = AccessTokenType.Reference,
    RequireConsent = false,
    RequirePkce = true
}

I now want to set up a reverse proxy gateway between the client application and the services that will exchange the reference token for a regular signed JWT before forwarding the request along. Before even setting up the gateway I am attempting to perform the exchange manually by calling the introspection endpoint using the reference token obtained from signing in.

I added an API which I called "gateway" to the identity server as described here, gave it a secret and successfully called this endpoint using the IntrospectionClient with the API's ID and secret, but I'm getting a response of active: false, and the identity server logs show an error that the token is missing the expected scope "gateway". The token information shown in the log shows only the openid scope.

new ApiResource("gateway"){
    ApiSecrets = { new Secret("test".Sha256()) }
}

This results in two log messages from IdentityServer:

fail: IdentityServer4.ResponseHandling.IntrospectionResponseGenerator[0]
      Expected scope gateway is missing in token
info: IdentityServer4.Endpoints.IntrospectionEndpoint[0]
      Success token introspection. Token active: True, for API name: gateway

So my take away from this is that there is some link missing between the API and the token that was issued, but I've tried every permutation of scopes and allowed scopes between the Client and ApiResource definitions that I could think of, but I cannot seem to get the expected result. I've read and reread the documentation several times and I cannot quite figure out the relationship between API and clients in this context. What sort of configuration is required to support this type of setup?

2

2 Answers

4
votes

It looks like your proxy is using gateway scope for introspection endpoint, and the problem is that your token does not have this gateway scope, so you always would get active: false in response.

You have two options here:

  1. Give gateway scope to your client and make it send the gateway scope along other scopes in authorize request.
new Client
{
    ClientId = "app",
    ClientName = "My Application",
    AllowedGrantTypes = GrantTypes.Code,
    RequireClientSecret = false,
    RedirectUris = { "http://site.example.com:3000/callback" },
    AllowedCorsOrigins = { "http://site.example.com:3000" },
    AllowedScopes = { "openid", "profile", "email", "gateway" }, // add gateway here
    AccessTokenType = AccessTokenType.Reference,
    RequireConsent = false,
    RequirePkce = true
}
  1. Write a custom IClaimService and add gateway scope to all access token.
public class CustomClaimService : IdentityServer4.Services.DefaultClaimsService
{
    public CustomClaimService(IProfileService profile, ILogger<DefaultClaimsService> logger)
        : base(profile, logger)
    {

    }

    public override Task<IEnumerable<Claim>> GetAccessTokenClaimsAsync(ClaimsPrincipal subject, Resources resources, ValidatedRequest request)
    {
        resources.ApiResources.Add(new ApiResource("gateway"));
        return base.GetAccessTokenClaimsAsync(subject, resources, request);
    }
}

You also need to register CustomClaimService to IServiceCollection before AddIdentityServer

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddTransient<IClaimsService, CustomClaimService>();
    services.AddIdentityServer();
    ...
}
1
votes

Your token has not been issued for the gateway scope. Your client is not allowed to request that scope:

AllowedScopes = { "openid", "profile", "email" },

So, if the apigateway call the introspection endpoint with this token the response will be allways active=false

I guess your api needs user information. If not you could use Client Credentials Grant in the apigateway to call your api.

If not, you have 2 solutions:

  1. Use the same scope in your apigateway and in your api and pass the token directly to your api where you will call the introspection endpoint. Note that your apigateway and your api are sharing audience and scopes so this is not the best option.

  2. Custom Delegation Grant. The apigateway and api have their own audiences and scopes. The apigateways acts as a Delegation Client who invoke the token endpoint of your idp to forward your user claims to a new token issued for your api. The IdentityServer4 documentation shows a way to implement a Delegation Grant using ExtensionGrants. You have to keep in mind that your apigateway is an apiresource and at the same time a delegation client.

You have a great post of Scott Brady about Delegation Patterns for OAuth 2.0.