L-Four
L-Four

Reputation: 13551

Access token doesn't contain 'sub' claim in access token after call to RequestClientCredentialsTokenAsync

I have set up a solution with the following three assets:

Currently, authentication works: if I try to access a protected resource in the MVC app, it redirects to IdentityServer4, then I can log on with my Facebook account, the user is provisioned and a subject ID is generated, and I'm redirected back to the MVC app. This works correctly.

I can also call a protected web API function from within the MVC site. However, in the claims that I receive in the web API, I don't receive subject ID.

If I investigate further, then I can see that in my MVC application, I get back an access token from the RequestClientCredentialsTokenAsync call, but it only contains:

{
  "nbf": 1548693531,
  "exp": 1548697131,
  "iss": "http://localhost:5000",
  "aud": [
    "http://localhost:5000/resources",
    "mgpApi"
  ],
  "client_id": "mgpPortal",
  "scope": [
    "mgpApi"
  ]
}

What I would like is that I also receive subject ID in this access token, so that I also have it in the web API function that I call.

My IdentityServer4 host is configured with:

public void ConfigureServices(IServiceCollection services)
{
    var builder = services.AddIdentityServer()
            .AddInMemoryIdentityResources(Resources.GetIdentityResources())
            .AddInMemoryApiResources(Resources.GetApiResources())                
            .AddProfileService<ProfileService>()
            .AddInMemoryClients(Clients.Get());
}

...and I'm registering a client with:

new Client
{
    EnableLocalLogin = false,

    ClientId = "mgpPortal",
    ClientName = "MGP Portal Site",
    AllowedGrantTypes = GrantTypes.ImplicitAndClientCredentials,

    // where to redirect to after login
    RedirectUris = { "http://localhost:5002/signin-oidc" },

    // where to redirect to after logout
    PostLogoutRedirectUris = { "http://localhost:5002/signout-callback-oidc" },

    // secret for authentication
    ClientSecrets =
    {
        new Secret("secret".Sha256())
    },

    AllowedScopes = new List<string>
    {
        IdentityServerConstants.StandardScopes.OpenId,
        IdentityServerConstants.StandardScopes.Profile,
        "mgpApi"
    },

    AllowOfflineAccess = true,
    AlwaysIncludeUserClaimsInIdToken = true,
    AlwaysSendClientClaims = true,
}

...and I also tried, as a test, to add claims with a ProfileService like:

public class ProfileService : IProfileService
{
    public Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        context.IssuedClaims.AddRange(context.Subject.Claims);
        context.IssuedClaims.Add(new Claim("sub", "test"));

        return Task.FromResult(0);
    }

    public Task IsActiveAsync(IsActiveContext context)
    {
        return Task.FromResult(0);
    }
}

But it doesn't make any difference.

My MVC client is configured with:

public void ConfigureServices(IServiceCollection services)
{
    JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

    services.AddAuthentication(options =>
    {
        options.DefaultScheme = "Cookies";
        options.DefaultChallengeScheme = "oidc";                 
    })
    .AddCookie("Cookies")
    .AddOpenIdConnect("oidc", options =>
    {                
        options.Authority = "http://localhost:5000";
        options.RequireHttpsMetadata = false;                
        options.ClientId = "mgpPortal";
        options.SaveTokens = true;                
    });           
}

So what should I add in order to receive subject ID in the access token?

If more information is required, please let me know. Any help is appreciated!

Upvotes: 2

Views: 1830

Answers (2)

DaniCE
DaniCE

Reputation: 2421

I was in a similar situation. After lot of proofs I have found that is very easy to use the same token of the user of a MVC application to access a different Web Api application. It is explained here:

public async Task<IActionResult> CallApi()
{
    var accessToken = await HttpContext.GetTokenAsync("access_token");

    var client = new HttpClient();
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
    ...
}

Here another version using IdentityModel:

public async Task<IActionResult> CallApi()
{
  var accessToken = await HttpContext.GetTokenAsync("access_token");
  var apiClient = new HttpClient();
  client.SetBearerToken(accessToken); 
  ....
  await client.PutAsync(url, json);   

Hope it could help...

Upvotes: 0

Vidmantas Blazevicius
Vidmantas Blazevicius

Reputation: 4812

You are using the wrong flow, use hybrid flow. In client credentials there is no concept of user subject hence the issue you are experiencing.

Upvotes: 2

Related Questions