8

So, I'm trying to implement an OIDC client application using ASP.NET Core 3.1. I am trying to leverage the .AddOpenIdConnect() and .AddJswtBearer() middleware. However, I need some clarification on what this middleware is doing.

Here is what I currently have for the middleware configuration:

.AddOpenIdConnect(options =>
{
    options.Authority = Configuration["auth:oidc:authority"];
    options.ClientId = Configuration["auth:oidc:clientid"];
    options.ClientSecret = Configuration["auth:oidc:clientsecret"];
    options.ResponseType = OpenIdConnectResponseType.Code;
    options.GetClaimsFromUserInfoEndpoint = true;
    options.SaveTokens = true;
})
.AddJwtBearer(options =>
{
    options.Authority = Configuration["auth:oidc:authority"];
    options.Audience = Configuration["auth:oidc:clientid"];
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidIssuer = Configuration["auth:oidc:authority"],
        ValidAudience = Configuration["auth:oidc:clientid"],
        ValidateIssuerSigningKey = true,
        ClockSkew = TimeSpan.Zero
     };
}

I notice that requests to the Authorization server's /.well-known/oidc-configuration and /.well-known/keys endpoints are requested when the application is first started, based on my Fiddler capture below

Fiddler capture of config and keys endpoint

Where does it do that?

I'm trying to also validate that the JWT received from the authorization server is valid (that it hasn't been tampered with between the time the server sent it, and the time that the client has received it). I understood this to happen when I added the TokenValidationParameters object in the .AddJwtBearer() middleware. To test this, I tried changing Valid Audience in the TokenValidationParameters to something like asdkwewrj which I know is not the valid audience for my token. But, I never got an error from the client saying that the audience was invalid. The authentication still worked, and I was able to access my secure dashboard still.

Another thing I'm trying to implement is refresh_token grant_type with this OIDC client. I thought that the options.saveTokens in the .AddOpenIdConnect() middleware would allow me to save the tokens. It looks like they're save as cookies, but these cookies look nothing like my token values (my access token is a JWT, but out of the cookies I see, none of them begin with ey).

In a nutshell, I'm trying to understand the following:

  1. Does this .AddJwtBearer() middleware validate the ID Token for me if I have the correct JwtBearerOptions defined (like I do above)? Or do I need to manually validate the ID token against the JWKs from the JWKs URI?
  2. If I have to manually validate the ID token using the JWKs from the JWKs URI, how do I store these JWKs when the middleware makes the request to the /.well-known/keys endpoint?
  3. How do I get the cookies that correspond to the access token and refresh token, and then send the refresh token to my authorization server?
  4. I noticed that I can utilize options.Events in both of these middlewares. Would any of those solve any of the items I'm trying to accomplish?
  5. Overall, what do these two middlewares handle for me, that I shouldn't need to manually do (i.e token validation and/or token renewal)?

Thank you! I am still fairly new to in-depth ASP.NET development like this, so I appreciate any responses.

Useme Alehosaini
  • 2,998
  • 6
  • 18
  • 26
deathcat05
  • 429
  • 1
  • 5
  • 18

2 Answers2

12

First of all, the OIDC authentication scheme and the JWT bearer authentication scheme are independent of each other. OIDC is mostly used for server-side authentication and will pretty much never be used on its own but always with the cookie scheme. The reason for this is that the OIDC scheme will just be used for the authentication but is not able to persist the information on its own. I’ve went into more details in a different answer of mine that also explains how the authentication flow works with OIDC.

As for JWT bearer, this authentication scheme will run on every request since it is completely stateless and expects clients to authenticate themselves using the Authorization header all the time. This makes it primarily used for protecting APIs since browsers wouldn’t be able to provide a JWT for normal browser requests.

So you should first ask yourself whether you are protecting your server-side web application (e.g. using Razor views or Razor pages) in which case you want to use OIDC and the cookies authentication scheme, or if you are protecting your API. Of course, the answer could be “both” in which case you want all of those three schemes but ASP.NET Core will not support this without further configuration.


With that being clarified, let’s get into your questions:

  1. The requests to /.well-known/oidc-configuration and /.well-known/keys are done by both the OIDC and the JWT bearer scheme in order to retrieve information from your identity provider. They will do that regularly to update their data, including information about the signining keys which they will use to validate the tokens. This happens within the scheme handler and is usually not visible to you.

  2. Correctly set up, the JWT bearer authentication will validate the token for you. It will do that by verifying the signatures using the retrieved signing keys, and then it may check additional properties like the specified audience or its lifetime.

  3. You shouldn’t ever need to validate tokens manually. That’s the job of the authentication scheme. You are using the authentication stack so you can just access the user principal within your app without doing anything.

  4. Cookies are protected using data protection so that they are safe against forgery. In order to retrieve the tokens stored with SaveTokens = true, you can use the GetTokenAsync method on your HTTP context.

  5. You can use the authentication events to add to the default behavior of the authentication schemes. For validating your tokens using the standard mechanisms, you shouldn’t need to though.

  6. There is just one middleware: The authentication middleware. It uses the configured authentication schemes to perform the authentication of users so that—once set up correctly—the authenticated user is available throughout the application, e.g. in controllers, MVC filters, Razor views, etc.

poke
  • 369,085
  • 72
  • 557
  • 602
  • Thank you for all this clarification. It has helped a lot. But, just a couple follow-up questions: 1. I don't need to worry about ID Token validation on the client-side, which means I don't need the `.AddJwtBearer()` middleware? 2. Is there anyway to see where the JWKs are stored by the middleware when the `/.well-known/keys` endpoint is requested? – deathcat05 Dec 21 '20 at 17:18
  • Another follow-up: I'm asking if there's a way to see the JWKs because I noticed in the `TokenValidationParameters` for the `.AddOpenIdConnect()` middleware, there is an option to set `ValidateIssuerSigningKey` and also an option to set `IssuerSigningKey(s)` but there is also an `IssuerSigningKeyValidator`. Without any of these set, the middleware still correctly validates the JWK? – deathcat05 Dec 21 '20 at 17:43
  • 1
    By default the [`JwtBearerOptions.ConfigurationManager`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.jwtbearer.jwtbeareroptions.configurationmanager?view=aspnetcore-5.0) is used to request the configuration from the metadata endpoint (`.well-known/openid-configuration` and the signing keys). You can however also configure the authentication configuration directly by setting [`JwtBearerOptions.Configuration`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.authentication.jwtbearer.jwtbeareroptions.configuration?view=aspnetcore-5.0). – poke Dec 21 '20 at 21:03
  • 1
    That configuration manager is also what will store the keys (and the configuration) until it gets refreshed again (which happens regularly). You could load it (from the options) and call its [`GetConfigurationAsync`](https://learn.microsoft.com/en-us/dotnet/api/microsoft.identitymodel.protocols.iconfigurationmanager-1.getconfigurationasync?view=azure-dotnet) method in order to access the configuration from the metadata endpoint. Usually, there’s no need to do that though since the authentication scheme will do that for you. – poke Dec 21 '20 at 21:05
  • Okay, great. Seems best to just let the authentication scheme handle everything for me then. – deathcat05 Dec 21 '20 at 23:31
3

Does this .AddJwtBearer() middleware validate the ID Token for me if I have the correct JwtBearerOptions defined (like I do above)? Or do I need to manually validate the ID token against the JWKs from the JWKs URI?

AddJwtBearer is only used by APIs to validate the access token and create a user (ClaimsPrincipal) out of it. It's all it does. It does not deal with id token.

In general its easier to put the API on a separate service, to make it more clear who is doing what. when you mix both the client and API in the same service, it can be harder to reason about it.

If I have to manually validate the ID token using the JWKs from the JWKs URI, how do I store these JWKs when the middleware makes the request to the /.well-known/keys endpoint? How do I get the cookies that correspond to the access token and refresh token, and then send the refresh token to my authorization server?

The ID token is validated and handled for you by AddOpenIdConnect. You dont need to validate the ID-token by yourself. AddOpenIdConnect will create the cookie and optionally store the tokens as well in the cookie.

Overall, what do these two middlewares handle for me, that I shouldn't need to manually do (i.e token validation and/or token renewal)?

To summarize:

Ues .AddOpenIdConnect() for the client, that allows the user to login.

Use .AddJswtBearer() for the backend APIs.

Token renewal is a different story that none of them handles out of the box. For that, you can consider to use IdentityModel.AspNetCore or do something by yourself.

To complement this answer, I wrote a blog post that goes into more detail about this topic: Debugging OpenID Connect claim problems in ASP.NET Core

Tore Nestenius
  • 16,431
  • 5
  • 30
  • 40
  • Okay, great. This is starting to make some sense. So if I want all the ID token parameters to be validated using the `.AddOpenIdConnect()` middleware, I can still utilize the `TokenValidationParameters`? Also, where can I view all the claim data about the authenticated user in my application? – deathcat05 Dec 21 '20 at 17:24
  • 1
    Once the user is authenticated, you can access `HttpContext.User` or one of the many `User` properties throughout the framework (e.g. on controllers, Razor views or Razor pages). – poke Dec 21 '20 at 21:09
  • The TokenValidator is used to validate both the access and ID-token. – Tore Nestenius Dec 22 '20 at 08:32