Ed Williams
Ed Williams

Reputation: 125

IdentityServer4: Quickstart always returns Unauthorized for API endpoint, with message "The audience 'https://localhost:5001/resources' is invalid"

Following the instructions at https://docs.identityserver.io/en/latest/quickstarts/1_client_credentials.html verbatim, I am unable to execute a client. Always returns a 401 Unauthorized.

The token generated is:

{
  "alg": "RS256",
  "kid": "57EDAEBEC68F3CAACE869E3FA226C0FF",
  "typ": "at+jwt"
}.{
  "nbf": 1593466354,
  "exp": 1593469954,
  "iss": "https://localhost:5001",
  "aud": "https://localhost:5001/resources",
  "client_id": "client",
  "jti": "C76BC9CB471ED81832A56B78059421FB",
  "iat": 1593466354,
  "scope": [
    "api1"
  ]
}.[Signature]

But I see no way to set the audience. :s

My Api

Startup.cs:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace api
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            services.AddAuthentication("Bearer")
                .AddJwtBearer("Bearer", options =>
                {
                    // Use our IS4 implementation as the authentication source.
                    options.Authority = "https://localhost:5001";
                    options.RequireHttpsMetadata = false;

                    options.Audience = "api1";
                });

            services.AddAuthorization(options =>
            {
                options.AddPolicy("ApiScopePolicy", policy =>
                {
                    policy.RequireAuthenticatedUser();
                    policy.RequireClaim("scope", "api1");
                });
            });
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();
            app.UseRouting();

            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

MyEndpointController

using System.Linq;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

namespace Api.Controllers
{
    [Route("MyEndpoint")]
    [Authorize]
    public class MyEndpointController : ControllerBase
    {
        [HttpGet]
        [Route("Get")]
        public IActionResult Get()
        {
            return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
        }
    }
}

My Identity Server implementation

Startup.cs

public class Startup
{
    public IWebHostEnvironment Environment { get; }

    public Startup(IWebHostEnvironment environment)
    {
        Environment = environment;
    }

    public void ConfigureServices(IServiceCollection services)
    {
        // uncomment, if you want to add an MVC-based UI
        services.AddControllersWithViews();

        var builder = services.AddIdentityServer(options =>
        {
            // see https://identityserver4.readthedocs.io/en/latest/topics/resources.html
            options.EmitStaticAudienceClaim = true;
        })
            .AddInMemoryIdentityResources(Config.IdentityResources)
            .AddInMemoryApiScopes(Config.ApiScopes)
            .AddInMemoryApiResources(Config.Apis)
            .AddInMemoryClients(Config.Clients)
            .AddJwtBearerClientAuthentication()
            ;

        // not recommended for production - you need to store your key material somewhere secure
        builder.AddDeveloperSigningCredential();
    }

    public void Configure(IApplicationBuilder app)
    {
        if (Environment.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        // uncomment if you want to add MVC
        app.UseStaticFiles();
        app.UseRouting();
        //-----------------------------------

        app.UseIdentityServer();

        // uncomment, if you want to add MVC
        app.UseAuthorization();
        app.UseEndpoints(endpoints =>
        {
            endpoints.MapDefaultControllerRoute();
        });
        //-----------------------------------
    }
}

Config.cs

public static class Config
{
    public static IEnumerable<IdentityResource> IdentityResources =>
        new IdentityResource[]
        { 
            new IdentityResources.OpenId()
        };

    public static IEnumerable<ApiResource> Apis =>
        new List<ApiResource>
        {
                    new ApiResource("api1", "My API")
        };

    public static IEnumerable<ApiScope> ApiScopes =>
        new ApiScope[]
        { 
            new ApiScope() {
                Description = "An example scope",
                DisplayName = "api1",
                Enabled = true,
                Name = "api1",
                ShowInDiscoveryDocument = true,
                UserClaims = new string[] {"UserClaim1", "UserClaim2"}
            }
        };

    public static IEnumerable<Client> Clients =>
        new Client[] 
        { 
            new Client() {
                ClientId = "client",

                // no interactive user, use the clientid/secret for authentication
                AllowedGrantTypes = GrantTypes.ClientCredentials,

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

                // scopes that client has access to
                AllowedScopes = { "api1" }
            }
        };
}

And my console program:

program.cs

using System;
using System.Net.Http;
using System.Threading.Tasks;
using IdentityModel.Client;
using Newtonsoft.Json.Linq;

namespace ClientConsoleApp
{
    class Program
    {
        static async Task Main(string[] args)
        {
            // discover endpoints from metadata
            var client = new HttpClient();
            var disco = await client.GetDiscoveryDocumentAsync("https://localhost:5001");
            if (disco.IsError)
            {
                Console.WriteLine(disco.Error);
                return;
            }

            Console.WriteLine("============================================================================================");
            Console.WriteLine("Discovery Document:");
            Console.WriteLine("============================================================================================");
            Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(disco));

            // request token
            var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
            {
                Address = disco.TokenEndpoint,

                ClientId = "client",
                ClientSecret = "secret",
                Scope = "api1"
            });

            if (tokenResponse.IsError)
            {
                Console.WriteLine(tokenResponse.Error);
                return;
            }

            Console.WriteLine("============================================================================================");
            Console.WriteLine("Token Response:");
            Console.WriteLine("============================================================================================");
            Console.WriteLine(tokenResponse.Json);

            const string endpoint = "https://localhost:6001/MyEndpoint/Get";
            Console.WriteLine("============================================================================================");
            Console.WriteLine($"Calling api endpoint {endpoint}");
            Console.WriteLine("============================================================================================");

            // call api
            var apiClient = new HttpClient(new LoggingHandler(new HttpClientHandler()));
            apiClient.SetBearerToken(tokenResponse.AccessToken);

            Console.WriteLine("============================================================================================");
            Console.WriteLine("Request");
            Console.WriteLine("============================================================================================");
            
            var response = await apiClient.GetAsync(endpoint);

            Console.WriteLine("============================================================================================");
            Console.WriteLine("Response");
            Console.WriteLine("============================================================================================");

            if (!response.IsSuccessStatusCode)
            {
                Console.WriteLine(response.StatusCode);
            }
            else
            {
                var content = await response.Content.ReadAsStringAsync();
                Console.WriteLine(JArray.Parse(content));
            }
        }
    }
}

The output is:

============================================================================================
Discovery Document:
============================================================================================
{"Policy":{"LoopbackAddresses":["localhost","127.0.0.1"],"Authority":"https://localhost:5001","AuthorityValidationStrategy":{},"RequireHttps":true,"AllowHttpOnLoopback":true,"ValidateIssuerName":true,"ValidateEndpoints":true,"EndpointValidationExcludeList":[],"AdditionalEndpointBaseAddresses":[],"RequireKeySet":true},"KeySet":{"Keys":[{"alg":"RS256","e":"AQAB","key_ops":[],"kid":"57EDAEBEC68F3CAACE869E3FA226C0FF","kty":"RSA","n":"oFo6iB0Kd-wzEFeR-fY12_8cF2uirsHI5FAtTAAOlAWUm5MRIPJjpXy8D4R9ZjU5750JUqcotQii8YF4DP_lN8Ro3SKFtI9HD4IazsX65ici2hhKSdAl4MEdUBRIgEdCwolQJgDOAhqls6WNqLRsh1Ify0EKI9AVKInwTbEXgCaHSsqGw8zubx8fSdQ4lgxQZGii792XYPVhFXMoom-6dVY9_7z5o5Or2sATdqaEAuLPLZLqMNVT284S9vMd4hxolIxVbuRgKQV4MZ-1mBK_C-GqjishVxdew6d_GasmRAt_2s0R4JlgZgeqzd7U2Agu5RETxpv6WUiDC9qCZnmXjQ","use":"sig","x5c":[],"KeySize":2048,"HasPrivateKey":false}]},"Issuer":"https://localhost:5001","AuthorizeEndpoint":"https://localhost:5001/connect/authorize","TokenEndpoint":"https://localhost:5001/connect/token","UserInfoEndpoint":"https://localhost:5001/connect/userinfo","IntrospectionEndpoint":"https://localhost:5001/connect/introspect","RevocationEndpoint":"https://localhost:5001/connect/revocation","DeviceAuthorizationEndpoint":"https://localhost:5001/connect/deviceauthorization","JwksUri":"https://localhost:5001/.well-known/openid-configuration/jwks","EndSessionEndpoint":"https://localhost:5001/connect/endsession","CheckSessionIframe":"https://localhost:5001/connect/checksession","RegistrationEndpoint":null,"FrontChannelLogoutSupported":true,"FrontChannelLogoutSessionSupported":true,"GrantTypesSupported":["authorization_code","client_credentials","refresh_token","implicit","urn:ietf:params:oauth:grant-type:device_code"],"CodeChallengeMethodsSupported":["plain","S256"],"ScopesSupported":["openid","api1","offline_access"],"SubjectTypesSupported":["public"],"ResponseModesSupported":["form_post","query","fragment"],"ResponseTypesSupported":["code","token","id_token","id_token token","code id_token","code token","code id_token token"],"ClaimsSupported":["sub","UserClaim1","UserClaim2"],"TokenEndpointAuthenticationMethodsSupported":["client_secret_basic","client_secret_post","private_key_jwt"],"HttpResponse":{"Version":"1.1","Content":{"Headers":[{"Key":"Content-Type","Value":["application/json; charset=UTF-8"]}]},"StatusCode":200,"ReasonPhrase":"OK","Headers":[{"Key":"Date","Value":["Mon, 29 Jun 2020 20:14:49 GMT"]},{"Key":"Server","Value":["Kestrel"]},{"Key":"Transfer-Encoding","Value":["chunked"]}],"TrailingHeaders":[],"RequestMessage":{"Address":"https://localhost:5001","ClientId":null,"ClientSecret":null,"ClientAssertion":{"Type":null,"Value":null},"ClientCredentialStyle":1,"AuthorizationHeaderStyle":0,"Parameters":{},"Version":"1.1","Content":null,"Method":{"Method":"GET"},"RequestUri":"https://localhost:5001/.well-known/openid-configuration","Headers":[{"Key":"Accept","Value":["application/json"]}],"Properties":{}},"IsSuccessStatusCode":true},"Raw":"{\"issuer\":\"https://localhost:5001\",\"jwks_uri\":\"https://localhost:5001/.well-known/openid-configuration/jwks\",\"authorization_endpoint\":\"https://localhost:5001/connect/authorize\",\"token_endpoint\":\"https://localhost:5001/connect/token\",\"userinfo_endpoint\":\"https://localhost:5001/connect/userinfo\",\"end_session_endpoint\":\"https://localhost:5001/connect/endsession\",\"check_session_iframe\":\"https://localhost:5001/connect/checksession\",\"revocation_endpoint\":\"https://localhost:5001/connect/revocation\",\"introspection_endpoint\":\"https://localhost:5001/connect/introspect\",\"device_authorization_endpoint\":\"https://localhost:5001/connect/deviceauthorization\",\"frontchannel_logout_supported\":true,\"frontchannel_logout_session_supported\":true,\"backchannel_logout_supported\":true,\"backchannel_logout_session_supported\":true,\"scopes_supported\":[\"openid\",\"api1\",\"offline_access\"],\"claims_supported\":[\"sub\",\"UserClaim1\",\"UserClaim2\"],\"grant_types_supported\":[\"authorization_code\",\"client_credentials\",\"refresh_token\",\"implicit\",\"urn:ietf:params:oauth:grant-type:device_code\"],\"response_types_supported\":[\"code\",\"token\",\"id_token\",\"id_token token\",\"code id_token\",\"code token\",\"code id_token token\"],\"response_modes_supported\":[\"form_post\",\"query\",\"fragment\"],\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\",\"private_key_jwt\"],\"id_token_signing_alg_values_supported\":[\"RS256\"],\"subject_types_supported\":[\"public\"],\"code_challenge_methods_supported\":[\"plain\",\"S256\"],\"request_parameter_supported\":true}","Json":{"issuer":"https://localhost:5001","jwks_uri":"https://localhost:5001/.well-known/openid-configuration/jwks","authorization_endpoint":"https://localhost:5001/connect/authorize","token_endpoint":"https://localhost:5001/connect/token","userinfo_endpoint":"https://localhost:5001/connect/userinfo","end_session_endpoint":"https://localhost:5001/connect/endsession","check_session_iframe":"https://localhost:5001/connect/checksession","revocation_endpoint":"https://localhost:5001/connect/revocation","introspection_endpoint":"https://localhost:5001/connect/introspect","device_authorization_endpoint":"https://localhost:5001/connect/deviceauthorization","frontchannel_logout_supported":true,"frontchannel_logout_session_supported":true,"backchannel_logout_supported":true,"backchannel_logout_session_supported":true,"scopes_supported":["openid","api1","offline_access"],"claims_supported":["sub","UserClaim1","UserClaim2"],"grant_types_supported":["authorization_code","client_credentials","refresh_token","implicit","urn:ietf:params:oauth:grant-type:device_code"],"response_types_supported":["code","token","id_token","id_token token","code id_token","code token","code id_token token"],"response_modes_supported":["form_post","query","fragment"],"token_endpoint_auth_methods_supported":["client_secret_basic","client_secret_post","private_key_jwt"],"id_token_signing_alg_values_supported":["RS256"],"subject_types_supported":["public"],"code_challenge_methods_supported":["plain","S256"],"request_parameter_supported":true},"Exception":null,"IsError":false,"ErrorType":0,"HttpStatusCode":200,"HttpErrorReason":"OK","Error":null}
============================================================================================
Token Response:
============================================================================================
{
  "access_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjU3RURBRUJFQzY4RjNDQUFDRTg2OUUzRkEyMjZDMEZGIiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE1OTM0NjE2OTAsImV4cCI6MTU5MzQ2NTI5MCwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NTAwMSIsImF1ZCI6Imh0dHBzOi8vbG9jYWxob3N0OjUwMDEvcmVzb3VyY2VzIiwiY2xpZW50X2lkIjoiY2xpZW50IiwianRpIjoiNEY5QzczMDZBRjdFMURDNjI3QkRBQTdCRjg4MjlDNTMiLCJpYXQiOjE1OTM0NjE2OTAsInNjb3BlIjpbImFwaTEiXX0.FxvjG89zv1a83MtyjJvzCA26g_VLO6HTElJuOSi1FOp_My1RGHB-mbg53E6jZF9Xq_pkAOak5SC73tMC0b3hcEGx9O1qsd9c_Q9ish2ffmCZZ34svkpsfZp3wjbS-xNyxq7mjSOg0JGpf3ML_eUz3TUcOa5Aba_evzmRDaVgAvEtsdM8D7lK_udnQmw0cDimc8vYaGSLIXJDfOhM9pb-8I67deElCxaIEG93CwRZV5bwQQQC3dLwihb51wndv962Kw0dPkIXrt1n7jwEQ4KAhBqVcP9DAgPTqem1Kix8Uq_P4wBTm_cMY7U7bCa-j6mvRZ8t7TxWARpylzlL-ojy7g",
  "expires_in": 3600,
  "token_type": "Bearer",
  "scope": "api1"
}
============================================================================================
Calling api endpoint https://localhost:6001/MyEndpoint/Get
============================================================================================
============================================================================================
Request
============================================================================================
Request:
Method: GET, RequestUri: 'https://localhost:6001/MyEndpoint/Get', Version: 1.1, Content: <null>, Headers:
{
  Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjU3RURBRUJFQzY4RjNDQUFDRTg2OUUzRkEyMjZDMEZGIiwidHlwIjoiYXQrand0In0.eyJuYmYiOjE1OTM0NjE2OTAsImV4cCI6MTU5MzQ2NTI5MCwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6NTAwMSIsImF1ZCI6Imh0dHBzOi8vbG9jYWxob3N0OjUwMDEvcmVzb3VyY2VzIiwiY2xpZW50X2lkIjoiY2xpZW50IiwianRpIjoiNEY5QzczMDZBRjdFMURDNjI3QkRBQTdCRjg4MjlDNTMiLCJpYXQiOjE1OTM0NjE2OTAsInNjb3BlIjpbImFwaTEiXX0.FxvjG89zv1a83MtyjJvzCA26g_VLO6HTElJuOSi1FOp_My1RGHB-mbg53E6jZF9Xq_pkAOak5SC73tMC0b3hcEGx9O1qsd9c_Q9ish2ffmCZZ34svkpsfZp3wjbS-xNyxq7mjSOg0JGpf3ML_eUz3TUcOa5Aba_evzmRDaVgAvEtsdM8D7lK_udnQmw0cDimc8vYaGSLIXJDfOhM9pb-8I67deElCxaIEG93CwRZV5bwQQQC3dLwihb51wndv962Kw0dPkIXrt1n7jwEQ4KAhBqVcP9DAgPTqem1Kix8Uq_P4wBTm_cMY7U7bCa-j6mvRZ8t7TxWARpylzlL-ojy7g
}

Response:
StatusCode: 401, ReasonPhrase: 'Unauthorized', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:
{
  Date: Mon, 29 Jun 2020 20:14:50 GMT
  Server: Kestrel
  WWW-Authenticate: Bearer error="invalid_token", error_description="The audience 'https://localhost:5001/resources' is invalid"
  Content-Length: 0
}


============================================================================================
Response
============================================================================================
Unauthorized

IdentityServer output:

[14:05:23 Information]
Starting host...

[14:05:24 Information] IdentityServer4.Startup
Starting IdentityServer4 version 4.0.0+1acafade44176bf817412aa4309d5dff6587a741

[14:05:24 Information] IdentityServer4.Startup
You are using the in-memory version of the persisted grant store. This will store consent decisions, authorization codes, refresh and reference tokens in memory only. If you are using any of those features in production, you want to switch to a different store implementation.

[14:05:24 Information] IdentityServer4.Startup
Using the default authentication scheme idsrv for IdentityServer

[14:05:24 Debug] IdentityServer4.Startup
Using idsrv as default ASP.NET Core scheme for authentication

[14:05:24 Debug] IdentityServer4.Startup
Using idsrv as default ASP.NET Core scheme for sign-in

[14:05:24 Debug] IdentityServer4.Startup
Using idsrv as default ASP.NET Core scheme for sign-out

[14:05:24 Debug] IdentityServer4.Startup
Using idsrv as default ASP.NET Core scheme for challenge

[14:05:24 Debug] IdentityServer4.Startup
Using idsrv as default ASP.NET Core scheme for forbid

[14:05:24 Information] Microsoft.Hosting.Lifetime
Now listening on: https://localhost:5001

[14:05:24 Information] Microsoft.Hosting.Lifetime
Application started. Press Ctrl+C to shut down.

[14:05:24 Information] Microsoft.Hosting.Lifetime
Hosting environment: Development

[14:05:24 Information] Microsoft.Hosting.Lifetime
Content root path: C:\Source\Repos\IdentityServer\code\IdentityServer

[14:05:26 Debug] IdentityServer4.Startup
Login Url: /Account/Login

[14:05:26 Debug] IdentityServer4.Startup
Login Return Url Parameter: ReturnUrl

[14:05:26 Debug] IdentityServer4.Startup
Logout Url: /Account/Logout

[14:05:26 Debug] IdentityServer4.Startup
ConsentUrl Url: /consent

[14:05:26 Debug] IdentityServer4.Startup
Consent Return Url Parameter: returnUrl

[14:05:26 Debug] IdentityServer4.Startup
Error Url: /home/error

[14:05:26 Debug] IdentityServer4.Startup
Error Id Parameter: errorId

[14:05:33 Debug] IdentityServer4.Hosting.EndpointRouter
Request path /.well-known/openid-configuration matched to endpoint type Discovery

[14:05:33 Debug] IdentityServer4.Hosting.EndpointRouter
Endpoint enabled: Discovery, successfully created handler: IdentityServer4.Endpoints.DiscoveryEndpoint

[14:05:33 Information] IdentityServer4.Hosting.IdentityServerMiddleware
Invoking IdentityServer endpoint: IdentityServer4.Endpoints.DiscoveryEndpoint for /.well-known/openid-configuration

[14:05:33 Debug] IdentityServer4.Endpoints.DiscoveryEndpoint
Start discovery request

[14:05:33 Debug] IdentityServer4.Hosting.EndpointRouter
Request path /.well-known/openid-configuration/jwks matched to endpoint type Discovery

[14:05:33 Debug] IdentityServer4.Hosting.EndpointRouter
Endpoint enabled: Discovery, successfully created handler: IdentityServer4.Endpoints.DiscoveryKeyEndpoint

[14:05:33 Information] IdentityServer4.Hosting.IdentityServerMiddleware
Invoking IdentityServer endpoint: IdentityServer4.Endpoints.DiscoveryKeyEndpoint for /.well-known/openid-configuration/jwks

[14:05:33 Debug] IdentityServer4.Endpoints.DiscoveryKeyEndpoint
Start key discovery request

[14:05:33 Debug] IdentityServer4.Hosting.EndpointRouter
Request path /connect/token matched to endpoint type Token

[14:05:33 Debug] IdentityServer4.Hosting.EndpointRouter
Endpoint enabled: Token, successfully created handler: IdentityServer4.Endpoints.TokenEndpoint

[14:05:33 Information] IdentityServer4.Hosting.IdentityServerMiddleware
Invoking IdentityServer endpoint: IdentityServer4.Endpoints.TokenEndpoint for /connect/token

[14:05:33 Debug] IdentityServer4.Endpoints.TokenEndpoint
Start token request.

[14:05:33 Debug] IdentityServer4.Validation.ClientSecretValidator
Start client validation

[14:05:33 Debug] IdentityServer4.Validation.BasicAuthenticationSecretParser
Start parsing Basic Authentication secret

[14:05:33 Debug] IdentityServer4.Validation.PostBodySecretParser
Start parsing for secret in post body

[14:05:33 Debug] IdentityServer4.Validation.ISecretsListParser
Parser found secret: PostBodySecretParser

[14:05:33 Debug] IdentityServer4.Validation.ISecretsListParser
Secret id found: client

[14:05:33 Debug] IdentityServer4.Stores.ValidatingClientStore
client configuration validation for client client succeeded.

[14:05:33 Debug] IdentityServer4.Validation.ISecretsListValidator
Secret validator success: HashedSharedSecretValidator

[14:05:33 Debug] IdentityServer4.Validation.ClientSecretValidator
Client validation success

[14:05:33 Debug] IdentityServer4.Validation.TokenRequestValidator
Start token request validation

[14:05:33 Debug] IdentityServer4.Validation.TokenRequestValidator
Start client credentials token request validation

[14:05:33 Debug] IdentityServer4.Validation.TokenRequestValidator
client credentials token request validation success

[14:05:33 Information] IdentityServer4.Validation.TokenRequestValidator
Token request validation success, {"ClientId": "client", "ClientName": null, "GrantType": "client_credentials", "Scopes": "api1", "AuthorizationCode": null, "RefreshToken": null, "UserName": null, "AuthenticationContextReferenceClasses": null, "Tenant": null, "IdP": null, "Raw": {"grant_type": "client_credentials", "scope": "api1", "client_id": "client", "client_secret": "***REDACTED***"}, "$type": "TokenRequestValidationLog"}

[14:05:33 Debug] IdentityServer4.Services.DefaultClaimsService
Getting claims for access token for client: client

[14:05:33 Debug] IdentityServer4.Endpoints.TokenEndpoint
Token request success.

[14:05:34 Debug] IdentityServer4.Hosting.EndpointRouter
Request path /.well-known/openid-configuration matched to endpoint type Discovery

[14:05:34 Debug] IdentityServer4.Hosting.EndpointRouter
Endpoint enabled: Discovery, successfully created handler: IdentityServer4.Endpoints.DiscoveryEndpoint

[14:05:34 Information] IdentityServer4.Hosting.IdentityServerMiddleware
Invoking IdentityServer endpoint: IdentityServer4.Endpoints.DiscoveryEndpoint for /.well-known/openid-configuration

[14:05:34 Debug] IdentityServer4.Endpoints.DiscoveryEndpoint
Start discovery request

[14:05:34 Debug] IdentityServer4.Hosting.EndpointRouter
Request path /.well-known/openid-configuration/jwks matched to endpoint type Discovery

[14:05:34 Debug] IdentityServer4.Hosting.EndpointRouter
Endpoint enabled: Discovery, successfully created handler: IdentityServer4.Endpoints.DiscoveryKeyEndpoint

[14:05:34 Information] IdentityServer4.Hosting.IdentityServerMiddleware
Invoking IdentityServer endpoint: IdentityServer4.Endpoints.DiscoveryKeyEndpoint for /.well-known/openid-configuration/jwks

[14:05:34 Debug] IdentityServer4.Endpoints.DiscoveryKeyEndpoint
Start key discovery request

Upvotes: 3

Views: 2953

Answers (4)

Michael Goll
Michael Goll

Reputation: 11

As far as I understood the documentation, the value "'https://localhost:5001/resources" is generated when you use options.EmitStaticAudienceClaim = true;.

If you need an aud claim, you can enable the EmitStaticAudience setting on the options. This will emit an aud claim in the issuer_name/resources format. If you need more control of the aud claim, use API resources.

I found it here: Authorization based on Scopes

To have "api1" in your "aud" section of the token you need to follow nahidf's and Vesko I's suggestions:

In you Config.cs File add an ApiResource:

public static IEnumerable<ApiResource> ApiResources =>
        new ApiResource[]
        {
            new ApiResource("api1", "Test API")
            {
                Scopes = { "api1.read", "api1.write" }
            }
        };

and in you Startup.cs add that resource to you ApiResources registration:

.AddInMemoryApiResources(Config.ApiResources)

After doing so, Identity Server will create tokens that contain this:

"aud": [
   "api1",
   "https://localhost:44300/resources"
],

Upvotes: 1

Vesko I
Vesko I

Reputation: 65

I had the same issue and found how to fix it. In Identity server 4 configuration you must add scope with name which you want to use.

Here is the example how to declare an api.

public static IEnumerable<ApiResource> Apis =>
                    new ApiResource[]
                    {
                         new ApiResource("adminApi", "Admin Panel Service")
                         {
                             Scopes = {
                                 "adminApi"
                             }
                         }
                    };

And add this to your configuration.

var builder = services.AddIdentityServer(options =>
            {
                options.Events.RaiseErrorEvents = true;
                options.Events.RaiseInformationEvents = true;
                options.Events.RaiseFailureEvents = true;
                options.Events.RaiseSuccessEvents = true;

                // see https://identityserver4.readthedocs.io/en/latest/topics/resources.html
                options.EmitStaticAudienceClaim = true;
            })
                .AddInMemoryIdentityResources(Config.IdentityResources)
                .AddInMemoryApiResources(Config.Apis)
                .AddInMemoryApiScopes(Config.ApiScopes)
                .AddInMemoryClients(Config.Clients)
                .AddAspNetIdentity<ApplicationUser>()
                .AddJwtBearerClientAuthentication();

Upvotes: 3

nahidf
nahidf

Reputation: 2394

With What your current settings on API, you need is an aud as api1 in the access_token. To fix just add the scopes to the API resource. Verify the generated token on https://jwt.ms/

public static IEnumerable<ApiResource> Apis =>
        new List<ApiResource>
        {
                    new ApiResource("api1", "My API")
                    {
                       Scopes = { "api1"}
                    }

        };

and revert back your API settings to have options.Audience = "api1";.

Read more about API resource here

Upvotes: 3

Ed Williams
Ed Williams

Reputation: 125

Ok, I found the way to fix this error but it opens up other issues.

First, looked at the generated JWT token. It shows Audience as "https://localhost:5001/resources". So I scanned the code for where the audience validation is done, which is in the API that authenticates the caller via IS4's Startup.cs file. Changed the audience value to the expected value, and it worked:

        services.AddAuthentication("Bearer")
            .AddJwtBearer("Bearer", options =>
            {
                // Use our IS4 implementation as the authentication source.
                options.Authority = "https://localhost:5001";
                options.RequireHttpsMetadata = false;

                //options.Audience = "api1";
                options.Audience = "https://localhost:5001/resources";
            });

This does not, however, tell me how that value was generated or where to override it, but does fix the issue.

Upvotes: 3

Related Questions