I am currently trying to integrate my client and API applications with Duende IdentityServer but I am encountering issues when it comes to authorizing the API.
After I get the access token from the Identity Server and attach it to the HttpClient so that I can make a call to my API endpoint which is marked [Authorize] I am getting a 401 status code and I am not sure why.
-> This is how my Identity Server app configuration looks like:
public static IEnumerable<IdentityResource> IdentityResources =>
new IdentityResource[]
{
new IdentityResources.OpenId(),
new IdentityResources.Profile(),
// -> adding the "roles" claim as part of the IdToken
new IdentityResource(
"roles",
"Your role(s)",
new [] { "role" })
};
public static IEnumerable<ApiResource> ApiResources =>
new ApiResource[]
{
new ApiResource("hcmapi",
"Human Capital Management API")
{
Scopes = { "hcmapi.fullaccess" }
}
};
public static IEnumerable<ApiScope> ApiScopes =>
new ApiScope[]
{
new ApiScope("hcmapi.fullaccess")
};
public static IEnumerable<Client> Clients =>
new Client[]
{
new Client()
{
ClientName = "Human Capital Management Client",
ClientId = "hcmClient",
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedGrantTypes = GrantTypes.Code,
// I did not want to reveal my URLs, hence the naming for the RedirectUris, but in my app they have the correct names
RedirectUris =
{
redirectUri
},
PostLogoutRedirectUris =
{
postLogoutRedirectUri
},
AllowedScopes =
{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"roles",
"hcmapi.fullaccess",
"offline_access"
},
AllowOfflineAccess = true,
RequireConsent = true
}
};
-> This is how my API's (Asp NET Core 6 web API) Program.cs class looks like:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = builder.Configuration["IdentityServerAppUrl"];
options.Audience = "hcmapi";
options.TokenValidationParameters = new()
{
NameClaimType = "given_name",
RoleClaimType = "role",
ValidTypes= new[] { "aj+jwt" }
};
});
-> This is how my Client (Blazor Server App) Program.cs class looks like:
builder.Services.AddAccessTokenManagement();
builder.Services.AddHttpClient("HcmAPI", config =>
{
config.BaseAddress = new Uri(builder.Configuration["DevelopmentApiURL"]);
config.DefaultRequestHeaders.Clear();
})
.AddUserAccessTokenHandler();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
builder.Services
.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = authorityUrl;
options.ClientId = "hcmClient";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.ClaimActions.Remove("aud");
options.ClaimActions.DeleteClaim("sid");
options.ClaimActions.DeleteClaim("idp");
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("roles");
options.Scope.Add("hcmapi.fullaccess");
options.Scope.Add("offline_access");
options.ClaimActions.MapJsonKey("role", "role");
options.TokenValidationParameters = new()
{
NameClaimType = "given_name",
RoleClaimType = "role"
};
});
-> These are the scopes that are present in the access_token: "scope": [ "openid", "profile", "roles", "hcmapi.fullaccess" ],
-> Inside one of my Client app's pages, I am doing the following:
protected async override Task OnInitializedAsync()
{
string apiAccessToken = await HttpContextAccessor.HttpContext!
.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
HttpEntitiesService.SetHttpClient(apiAccessToken!);
await HandleRetrievingEmployees();
StateHasChanged();
}
Inside Blazor in the OnInitializedAsync method I retrieve the accessToken from Identity server and attach it as a Bearer token to the instance of the HttpClient I am creating.
However, if I try to use that instance of HttpClient to make a request to an endpoint which is marked with [Authorize], which is what await HandleRetrievingEmployees() does it returns a status code 401, and I do not understand why.
Could you look over and tell me what am I missing? If I have to provide more information/code let me know. I tried to be as specific and also concise as possible. Thank you in advance!