1

I am using IdentityModel.AspNetCore and .AddClientAccessTokenHandler() extension to automatically supply HttpClient with access token (at least that is what I understand I can use it for) to an API. Some API endpoints are authorized based on a role. But for some reason, the access token that is added to the request does not contain the role claim. If I do not use the .AddClientAccessTokenHandler() and manually retrieve the token and set it using SetBearerToken(accessTone) then I can reach my role authorized endpoint.

My startup is:

services.AddAccessTokenManagement(options =>
{
    options.Client.Clients.Add("auth", new ClientCredentialsTokenRequest
    {
        Address = "https://localhost:44358/connect/token", 
        ClientId = "clientId",
        ClientSecret = "clientSecret",
    });
});

WebApi call:

var response = await _httpClient.GetAsync("api/WeatherForecast/SecretRole");

Identity server configuration:

public static IEnumerable<ApiResource> GetApis() =>
    new List<ApiResource>
    {
        new ApiResource("WebApi", new string[] { "role" })
            { Scopes = { "WebApi.All" }}
    };

public static IEnumerable<ApiScope> GetApiScopes() =>
    new List<ApiScope>
        { new ApiScope("WebApi.All") };

public static IEnumerable<IdentityResource> GetIdentityResources() =>
    new List<IdentityResource>
    {
        new IdentityResources.OpenId(),
        new IdentityResources.Profile(),
        new IdentityResource 
        {
            Name = "roles",
            UserClaims = { "role" }
        }
    };

public static IEnumerable<Client> GetClients() =>
    new List<Client>
    {
        new Client
        {
            ClientId = "clientId",
            ClientSecrets = { new Secret("clientSecret".ToSha256()) },
            AllowedGrantTypes = 
            { 
                GrantType.AuthorizationCode, 
                GrantType.ClientCredentials
            },
            AllowedScopes =
            {
                "WebApi.All",                        
                IdentityServerConstants.StandardScopes.OpenId,
                IdentityServerConstants.StandardScopes.Profile,
                "roles"
            },
            RedirectUris = { "https://localhost:44305/signin-oidc" },
            PostLogoutRedirectUris  = { "https://localhost:44305/Home/Index" },
            AlwaysIncludeUserClaimsInIdToken = false,
            AllowOfflineAccess = true,
        }
    };

For testing purposes I add users manually from Program.cs

public static void Main(string[] args)
{
    var host = CreateHostBuilder(args).Build();
    using (var scope = host.Services.CreateScope())
    {
        var userManager = scope.ServiceProvider.GetRequiredService<UserManager<IdentityUser>>();
        AddUsers(userManager).GetAwaiter().GetResult();
    }
    host.Run();
}

private static async Task AddUsers(UserManager<IdentityUser> userManager)
{
    var adminClaim = new Claim("role", "Admin");
    var visitorClaim = new Claim("role", "Visitor");

    var user = new IdentityUser("Admin");
    await userManager.CreateAsync(user, user.UserName);
    await userManager.AddClaimAsync(user, adminClaim);

    user = new IdentityUser("Visitor");
    await userManager.CreateAsync(user, user.UserName);
    await userManager.AddClaimAsync(user, visitorClaim);
}

So if I use manual access token retrieval and add it myself to the HttpClient headers, then my endpoint is reached and returns expected response. If I use .AddClientAccessTokenHandler(), I get 403 - Forbidden. What am I missing?

mikeyy
  • 835
  • 1
  • 10
  • 18
  • Do you create the _httpClient as such? : `_httpClient = factory.CreateClient("auth");` – Stefan Jul 13 '20 at 11:17
  • 1
    Actually...your comment give me something to think about and I managed to fix it. To my StartUp I had to add services.AddHttpClient("auth", (client) => { client.BaseAddress = new Uri("https://localhost:44367/"); }) otherwise I was getting an exception of incorrect Uri. So that means that the key I added in Options.Client.Clients.Add has to match the key that is added in services.AddHttpClient for a particular match Client<=>Api? – mikeyy Jul 13 '20 at 11:52
  • Yes, I believe so - that way you're able to retrieve various of HttpClients for different API's. - I'll add it as answer; so you can upvote if you find it appropriate. – Stefan Jul 13 '20 at 12:02
  • Please do. Now originally, I had a bit different setup, but your input and some additional experimentation finally let me arrange in my head how this things are working. Thanks again. – mikeyy Jul 13 '20 at 12:36

1 Answers1

5

Since you are registering the client under the name auth, you also should retrieve it as such.

This basically means I expect you to use something like this, or it's equivalent:

_httpClient = factory.CreateClient("auth");

Basically this mechanism ensures you're able to retrieve HttpClients for various API's and settings.

ps. I am on mobile; and currently not very good access to my resources.

Stefan
  • 17,448
  • 11
  • 60
  • 79