Closed b3n3d17 closed 11 months ago
IdentityServer enforces that the subject, issuer, and client_id all match, because the semantics of our secret validation is that we want the client application to prove that it possesses a cryptographic secret. We do that by having the client sign a token with a private key and validate it using its public key. We don't have a mechanism for establishing trust with a third party that might issue tokens on behalf of the client. This does go beyond what is explicitly required by the specification, but the spec also says that implementations are allowed to add their own rules:
Application of additional restrictions and policy are at the discretion of the authorization server.
We require the jti claim because we use it to perform replay detection. While the spec says that tokens "MAY" include jti (MAY making it optional, so that spec-compliant implementations could accept a token without one), it also says that authorization servers may use the jti to do replay detection, as we do. Since we've decided to do so, we add the restriction that the jti must be present.
If you want different behavior, you can implement your own ISecretValidator
. All of the behavior that I'm describing above is implemented in the PrivateKeyJwtSecretValidator
(which implements that interface). You could create your own implementation of the ISecretValidator
that didn't do replay detection and so didn't require the jti and that trusted jwts issued by some other issuer. Then just register the validator with DI. The way that the validation works, if any validator is able to validate, then the secret is trusted, so the existing validator not being able to validate shouldn't be an issue. I suppose it might waste resources, so if you wanted to you could avoid registering the validator in the first place. It gets registered by the AddJwtBearerClientAuthentication
. Under the covers, that method is just registering a few things in DI. You could do that yourself, but leave out the validator you don't want:
// This is the IdentityServer method
public static IIdentityServerBuilder AddJwtBearerClientAuthentication(this IIdentityServerBuilder builder)
{
builder.AddSecretParser<JwtBearerClientAssertionSecretParser>();
builder.AddSecretValidator<PrivateKeyJwtSecretValidator>();
return builder;
}
// So do this instead of a call to AddJwtBearerClientAuthentication
builder.AddSecretParser<JwtBearerClientAssertionSecretParser>();
builder.AddSecretValidator<YourSecretValidator>(); // TODO, create your secret validator class
Also, the OIDC spec mandates our behavior:
private_key_jwt Clients that have registered a public key sign a JWT using that key. The Client authenticates in accordance with JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants [OAuth.JWT] and Assertion Framework for OAuth 2.0 Client Authentication and Authorization Grants [OAuth.Assertions]. The JWT MUST contain the following REQUIRED Claim Values and MAY contain the following OPTIONAL Claim Values:
iss REQUIRED. Issuer. This MUST contain the client_id of the OAuth Client. sub REQUIRED. Subject. This MUST contain the client_id of the OAuth Client.
Hi Joseph,
Thank you for the detailed descriptions. Also thank you for the hint, that the oidc spec has different required fields than the RFC I was looking at. Turns out my idea is not OIDC compliant after all ... ;(. I also did not realize that it was "that easy" to customize the validation behaviour, thanks for the hints.
Greetings Benedikt
Hi Joe,
you have implemented both OpenID Connect and OAuth specifications. However, you are enforcing the OpenID Connect specification processing rules by default and you require the implementation of a custom validator to get ordinary OAuth 2.0 handling.
In this specific case, the jti is an optional claim in the RFC 7523 specification and the issue does not need to be populated with the client_id.
I am wondering whether you have thought about configuring the resource server (relying party) such that a developer can conveniently switch between the OAuth specification behavior and the Open ID Connect behavior.
This isn't something we've got on our backlog, but it is helpful to hear feature requests. One thing that can help us as we prioritize feature requests is if you can describe your use case a bit more.
That said, it's not initially obvious to me what behavior IdentityServer actually should have in a "non-OIDC" mode. RFC 7523 says that the OP MAY use JTI to prevent replay, and we choose to do so. It wouldn't be possible to do that without a JTI, so it seems reasonable to require it if we're going to do replay detection. So this "non-oidc" mode flag would be turning that off, I guess? The issuer could theoretically be something other than the client id, but what value would you use? I suppose new config could control the expected issuer, but that new config should only be applicable in this mode. I would also assume that it is an error to configure a client with identity resource scopes and this non-oidc flag. And I'm sure there are other places where the OAuth specs make things optional or leave them to the digression of the authorization server but OIDC makes mandatory or explicit. We would have to review things pretty carefully to make sure we catch as much of that kind of thing as possible, and decide what the behavior should be if oidc is disabled. So my initial reaction is that this seems like it would take quite a bit of effort to add to IdentityServer itself. It also seems like there are enough places where things can be customized that you could get this behavior if you really needed it today.
For historical context, we've always been OIDC focused. So when there have been ambiguities between OIDC and OAuth, we've typically landed on the OIDC spec side of things.
See below my attempt at briefly describing the use case:
You work with the following:
Your requirements are:
Your solution:
The result: You have microservices with automagically rotating client credentials, which are short lived, and which you dont have to manage yourself.
The limitation:
My "current" PoC involves writing (as you described earlier - thank you for that) a new validator. Basically hard coding the issuer validation to the k8s issuer and removing the code where the jti is validated. However, my initial "feeling" is that for "production" it would be nicer to add this to the client configuration itselfe.
Maybe a oidc=false switch and then iss = .... .
Did that help in describing the use case, and on that note do you also see the benefit of this use case compared to using client_id and secrets?
Thanks for the description - it's always interesting to hear how people are using IdentityServer! The way that you're customizing things seems like a good example of how we envision the flexibility of IdentityServer - a mix of configuration and built-in features AND the ability to replace functionality through custom code. This really feels like your requirements and design are specialized enough that it should be a customization. If you run into limitations in the abstractions that create problems as you're implementing the customization, we'd really like to hear about that though.
Are there any more open questions on this issue or can we go ahead and close it?
Which version of Duende IdentityServer are you using? v6
Which version of .NET are you using? dotnet --version 7.0.400
Describe the bug
The private key JWT validation logic rejects my authentication request. Here the 2 screenshot of the relevant error messages:
To Reproduce
Expected behavior
I expect the validation logic to accept my Token even though subject and issuer are not equal and i do not have a JTI.
Additional context
Hi, i am trying to use k8s serviceaccount token projection to authenticate my microservice.
However, the provided Token Validation logic is stricter than required by [RFC7523 - Section 3](https://datatracker.ietf.org/doc/html/rfc7523#secti
on-3). Therefore, the authentication fails.
Dudende Identiy Server v6 checks for a JTI and it enforces the issuer to be the same as the client_id. Both checks are not required by the RFC (as far as I can read at least). I would like to have a "setting" which would facilitate only the minimum required checks. Currently I do not see any obvious way to change that behavior. Am I missing something obvious here?
I am using the following configuration:
And the following client configuration.
Here a token which I manipulated to be accepted
Here the token which k8s actually provides me
Thank you for your help