Search code examples
asp.net-coreauthorizationjwtidentityserver4

Correct way to setup IdentityServer4 Cookies for login, JWT token for API authorization


We have one application that uses an IdentityServer4 cookies authorization scheme for user login like this:

services.AddAuthentication(options =>
        {
            options.DefaultAuthenticateScheme = "Cookies";
            options.DefaultChallengeScheme = "oidc";
        })
        .AddCookie("Cookies")
        .AddOpenIdConnect("oidc", options =>
        {
            options.SignInScheme = "Cookies";
            options.Authority = <local IDP server with IdentityServer4>;
            options.ClientId = <ClientId>;
            options.ClientSecret = <secret>
            options.ResponseType = "code id_token";
            options.SaveTokens = true;
            options.GetClaimsFromUserInfoEndpoint = true;
            options.Scope.Add("openid");
            options.Scope.Add("profile");
            options.Scope.Add("offline_access");
        })

The client on the IDP looks like this:

new Client
{
    ClientId = <ClientID>,
    ClientName = <ClientName>,
    AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
    RequireConsent = true,
    ClientSecrets = { new Secret(<secret>.Sha256()) },
    AllowOfflineAccess = true,
    RedirectUris = { "http://" + ip + "/signin-oidc" },
    PostLogoutRedirectUris = { "http://" + ip + "/signout-callback-oidc" },
    AllowedScopes = {
            IdentityServerConstants.StandardScopes.OpenId,
            IdentityServerConstants.StandardScopes.Profile
        }
};

We also have a WebAPI located at /api/...

The goal is to add JWT Bearer Token authorization to the API. For that I have added the following code:

        .AddJwtBearer(jwtOptions =>
        {
            jwtOptions.Authority = <local IDP server with IdentityServer4>;
            jwtOptions.Audience = <ClientID>
            jwtOptions.SaveToken = true;
        })

The API is guarded with

[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

After login, I get the token with

await HttpContext.GetTokenAsync("access_token");

However, when I try to use that token to access the API, I get the following error:

AuthenticationFailed: IDX10214: Audience validation failed. Audiences: 'http://localhost:50059/resources'. Did not match: validationParameters.ValidAudience: ClientID or validationParameters.ValidAudiences: 'null'.

And indeed, when I decode the token using jwt.io, aud is set to http://localhost:50059/resources, while the ClientID appears as a new field 'client_id': '<ClientID>'.

What I found out so far suggests that aud is always set to <idp>/resources for access tokens and that API access is handled with scope claims inside the token. However, I do not understand how to set that up correctly.

Does anybody know where the problem lies and how I can fix it?


Solution

  • Turns out that the solution was quite simple. As suspected, it had to do with scopes. I tried to solve that earlier but it kept saying "invalid scopes"...because I never created the API resource, duh.

    1. Create new API resource in your IDP server new ApiResource("api-name", "API Name")
    2. Add new API resource as AllowedScope to Client client.AllowedScopes.Add("api-name")
    3. Ask for Scope in OpenIdConnect authentication options.Scopes.Add("api-name")
    4. Set Audience (or ApiName if using IdentityServer authentication handler) to API resource options.Audience = "api-name" or options.ApiName = "api-name"

    With that I was now able to use the access token to access my API.