简体   繁体   中英

Unable to Validate JWT or access_token in API with IdentityServer4

I'm working on building out some new functionality where we have an existing system. Our goal is to have different Angular SPA's communicate with APIs to present a new interface for our legacy app. A team has set up IdentityServer4 to use the legacy application for credentials verification.

The SPA, written in Angular 7, uses the implicit flow to authenticate. Response type is "token id_token" I've been able to get this flow to work insomuch as I can have an unauthenticated user come to the site, get kicked over to the IdentityServer authentication screens, log in with correct credentials, and then redirected back to the app. When I get the token back the claims object has a single value for aud , the client_id for the SPA, for example, spa-client . The access_token and id_token are both a JWT with RS256 as the signature method. Lastly, I've created an interceptor in the SPA to send the JWT (in actuality it just uses whatever value is sent back as the access_token) as the "Bearer" token on API requests.

I have now created a .NET Core 2.2 WebApi app to build out a service API to be consumed by the SPA. This app uses the IdentityServer4.AccessTokenValidation package to "protect" the API I've read the section in the IdentityServer4 docs. In my ConfigureServices method I've written the following to configure auth:

services    
    .AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
    .AddIdentityServerAuthentication(options =>
    {
        if (string.IsNullOrWhiteSpace(serviceConfiguration.Authentication.Authority)) throw new ConfigurationException("", nameof(serviceConfiguration.Authentication.Authority), "An authority must be defined.");
        if (string.IsNullOrWhiteSpace(serviceConfiguration.Authentication.ApiName)) throw new ConfigurationException("", nameof(serviceConfiguration.Authentication.ApiName), "An api name must be defined.");

        options.Authority = serviceConfiguration.Authentication.Authority;
        options.ApiName = serviceConfiguration.Authentication.ApiName;

        if (!string.IsNullOrWhiteSpace(serviceConfiguration.Authentication.ApiSecret))
        {
            options.ApiSecret = serviceConfiguration.Authentication.ApiSecret;
        }

        options.RequireHttpsMetadata = false;
        options.SupportedTokens = SupportedTokens.Both;
    }

serviceConfiguration is a strongly type representation of my JSON appsettings.json

My understanding is that with options.SupportedTokens = SupportedTokens.Both will check the JWT and do introspection.

Now that all this is setup I've taken the simple example ValuesController that is created by the API template and added the [Authorize] attribute to it. Doing a simple GET request to this endpoint in Postman returns a 401 as I would expect.

Next I try logging into the SPA and going to a test page which will do the same but with the interceptor will have the bearer token, the access_token, or basically the JWT. I still get a 401 from the service.

Looking at the logs output by the service I don't see anything specific that is helpful:

info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1] Request starting HTTP/1.1 OPTIONS http://localhost:5001/api/values info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1] Request starting HTTP/1.1 OPTIONS http://localhost:5001/api/values info: Microsoft.AspNetCore.Cors.Infrastructure.CorsService[4] CORS policy execution successful. info: Microsoft.AspNetCore.Cors.Infrastructure.CorsService[4] CORS policy execution successful. info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2] Request finished in 61.1352ms 204 info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2] Request finished in 61.1272ms 204 info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1] Request starting HTTP/1.1 GET http://localhost:5001/api/values application/json info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1] Request starting HTTP/1.1 GET http://localhost:5001/api/values application/json info: Microsoft.AspNetCore.Cors.Infrastructure.CorsService[4] CORS policy execution successful. info: Microsoft.AspNetCore.Cors.Infrastructure.CorsService[4] CORS policy execution successful. info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] Executing endpoint 'Examples.TestApp.API.Web.Controllers.ValuesController.Get (Examples.TestApp.API.Web)' info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0] Executing endpoint 'Examples.TestApp.API.Web.Controllers.ValuesController.Get (Examples.TestApp.API.Web)' info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1] Route matched with {action = "Get", controller = "Values"}. Executing action Examples.TestApp.API.Web.Controllers.ValuesController.Get (Examples.TestApp.API.Web) info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1] Route matched with {action = "Get", controller = "Values"}. Executing action Examples.TestApp.API.Web.Controllers.ValuesController.Get (Examples.TestApp.API.Web) info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2] Authorization failed. info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2] Authorization failed. info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[3] Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'. info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[3] Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'. info: Microsoft.AspNetCore.Mvc.ChallengeResult[1] Executing ChallengeResult with authentication schemes (). info: Microsoft.AspNetCore.Mvc.ChallengeResult[1] Executing ChallengeResult with authentication schemes (). info: IdentityServer4.AccessTokenValidation.IdentityServerAuthenticationHandler[12] AuthenticationScheme: Bearer was challenged. info: IdentityServer4.AccessTokenValidation.IdentityServerAuthenticationHandler[12] AuthenticationScheme: Bearer was challenged. info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2] Executed action Examples.TestApp.API.Web.Controllers.ValuesController.Get (Examples.TestApp.API.Web) in 166.3038ms info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2] Executed action Examples.TestApp.API.Web.Controllers.ValuesController.Get (Examples.TestApp.API.Web) in 162.8827ms info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] Executed endpoint 'Examples.TestApp.API.Web.Controllers.ValuesController.Get (Examples.TestApp.API.Web)' info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1] Executed endpoint 'Examples.TestApp.API.Web.Controllers.ValuesController.Get (Examples.TestApp.API.Web)' info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2] Request finished in 459.6677ms 401 info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2] Request finished in 473.7648ms 401

Setting the logging to Trace didn't reveal anything else.

I did try going to Postman to see if I could do introspection on the token manually. I set up authorization to basic and set the Username to "test-api", the api id, and the password that I set in its client secret. In the body a set it to x-www-form-urlencoded with a key of "token" and the value the access_token. I get a 200 OK with claims along with "active: true". Seems like that should be good, right?

I don't own the IdentityServer implementation so I'm not quite sure what's going on at that end except for being able to log into the admin UI. I've logged into the admin UI for the identity server and double checked the settings for the client and the API. The client gets granted a "api" scope and the API gets the same scope "api". This is shown in the scope that is sent back in the introspection response as well as the token that I get from the client.

I've also tried setting the options.SupportedTokens = SupportedTokens.Reference and options.SupportedTokens = SupportedTokens.JWT . Neither had any change. :(

What am I doing wrong? Is this a configuration issue or me being an idiot (probably the latter).

EDIT : RE access_token and id_token

I was incorrect in the original question. The tokens are different (I need to look at more than just the header portion).

The audiences are weird. I've never paid attention to a possible difference here when decoding. My powers of perception must suck!

The access token has aud: "http:///resources" The id_token has aud: "spa-client"

The access_token and id_token are both the same and are a JWT with RS256 as the signature method.

The access_token and id_token should not the same . For id_token the aud claim in token should be the name of the client , but in access token , your api name should be include in the aud , so that your api resource could validate that the access token is issued to make "my" api calls. You can use online tool like jwt.io to decode the claims in JWT tokens . Since you haven't post the configuration of client & IDS4 , you should check the response type,scope ... If using Fiddler to trace the request , it should like :

GET /connect/authorize/callback?client_id=js&redirect_uri=http%3A%2F%2Flocalhost%3A5003%2Fcallback.html&response_type=id_token%20token&scope=openid%20profile%20api1&state=xxxxxxxxxxxxxxx&nonce=xxxxxxxxxxxxxxxxxxx

In addition , from document : http://docs.identityserver.io/en/latest/topics/grant_types.html

For JavaScript-based applications, Implicit is not recommended anymore. Use Authorization Code with PKCE instead.

You can click here for code flow code sample .

Update :

the audience of access token should also include api name , apart from "http:///resources :

在此处输入图片说明

You could click here for code samples .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM