-
Notifications
You must be signed in to change notification settings - Fork 6k
"client secret jwt" and "private key jwt" auth methods #8445
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Hi, As I mentioned in the original ticket, I'm interested in helping out with this PR. We are library developers and our goal is to enable JVM developers in our organization to use spring-security with OAuth 2.0. Our Authorization Server is going to be PingFederate and our security engineers decided to require private_key_jwt in all cases when it's possible. Because we don't know how our users are going to want to use it, we're looking for a fairly complete implementation. This is the initial design I came up with (some of it is already addressed in this PR, some of it is new or slightly different, I don't insist on the structure and naming, only the functionality):
Open questions:
@jgrandja could you please have a look at this and say if you agree / give your opinion on the above "open questions"? Thanks a lot to both of you for working on this! |
@krajcsovszkig-ms My suggested changes are definitely incomplete, but I tried to capture the major pieces I minimally needed for my use case. I anticipated there would be lots of changes and of course tests to account for everything. I tried to make my implementation as generic as possible for now, but I think we're mostly on the same page. The one thing I don't necessarily agree with is storing the secret directly in a config. It sort of eliminates the enhanced security benefit of "private_key_jwt" that allows the secret to be locked behind a keystore in the first place. Especially if that configuration may move around in different environments and be exposed -- similarly to other auth methods. I did attempt to maintain the "client-secret" attribute for the "client secret jwt" use case, but not sure if I did everything necessary there since I was focused on the "private key jwt" use case. I have comments in the code where the JWT is signed that explain how one would create a keystore and extract the public key from it to be registered with an authorization server. This would be useful documentation on Spring's part; however, I'm open to having multiple ways of accomplishing signing that can be up to the user. Personally, I like the idea of the key store. I can pass mine around as a Kubernetes secret and mount it in my gateway API. If there's a cleaner way to add key store configs for this purpose, but my first pass was closely emulating the way a key store would be set up for SSL -- but nested in the oauth specific config. It would definitely be nice to get more Spring Security eyes on this, since I am not as familiar with the Spring codebase conventions, etc. |
Just circling back to your question 1 of three: I had the same thought, but AFAIK there is a subtle distinction between the way those secrets are shared. For example, Google generating a secret when I register with them versus me signing my own JWT with that secret and sharing it ahead of time with the authorization provider. A slight difference in responsibility. I could be mistaken about this, and it may be fundamentally the same problem for "client secret jwt". For "private key jwt", the secret isn't what I have to register with Login.gov explicitly ahead of time -- it is the corresponding public key of what I used to sign the JWT. See my signing functions for both auth methods in
|
Good point about the key security, it has occurred to me as well, how safe is it to keep these in Strings, but I haven't looked into the alternatives yet. If Spring offers a standard way for it, I believe we should use that. I'm not sure about including reading the key store in the |
Agreed. I wasn't really sure about my This was a feature that I really wanted to be built into Spring to make the auth method easier to understand and integrate quickly. Not having this built-in was the major point of confusion is getting my custom code with Login.gov working (apart from documentation). If a user wants the flexibility to sign themselves in a custom way, they should be able to. Authorization services can have widely varying requirements. However, I believe both these new auth methods require signing with an "HMAC SHA algorithm", and that is mostly orthogonal to other requirements that you might run into. It seems reasonable, therefore, to isolate the signing as I did and allow to point to a JKS key and algorithm -- but to default to "SHA-256", for instance. I would hope whatever solution is made, it allows me to point to a keystore/key and algorithm in a config, somehow, some way. |
This is the RFC on the supported algorithms: https://tools.ietf.org/html/rfc7518#section-3.1 HMAC-SHA requires a shared secret (so it's only usable for client_secret_jwt), while the others require a private-public key pair (private_key_jwt). The PS* algorithms are not supported in PingFederate which we'll be using, not sure about others. I agree it should be as simple as possible to inject the keys from a keystore, I'm just not sure it should be the responsibility of the Parsing a |
@forgo @krajcsovszkig-ms Thank you for all the details! Sorry for the delay in my response but I've been juggling quite a few tasks :) I took a look at the PR and here is my initial feedback:
I'd like to propose the following next steps to move this feature forward. First off, let's start simple and build it using an iterative approach. I'm going to propose for the initial iteration we implement Here is my thought for initial design:
Let me know your thoughts on this design. Before you proceed with the proposed updates, let's first align on design. |
@jgrandja I like your suggestions so far, and it's a good idea to start with the simple case first. I'm going to be a bit busier this week, but I am open to @krajcsovszkig-ms starting to make changes on this branch if they are more eager to get started on your suggestions. |
Thanks @jgrandja for having a look. Your iterative approach for the implementation sounds good. I think the work on the encoders in spring-security-oauth2-jose is fairly straightforward and independent from the rest, so it could be spun out in a separate PR and worked on right now, WDYT? Part of this though is rethinking the About putting all the PKI-stuff in the encoder factory, how exactly would the users use that? For the decoders it's quite straightforward because everything either comes through the wire (JWS algorithm, JWT claims) or is already in the Since the client secret is already in the I'm not sure about private keys, but if we are starting with One more thing is the |
Agreed, this can be submitted in a separate PR. Feel free to take this on.
The JOSE headers are in
We may add support for OpenID Connect Dynamic Client Registration 1.0 at a later point so it might be useful to read that spec to see how
Yes, let's define both auth methods as I like to align the implementation with the spec language. |
All 3 are currently mandatory (must be non-empty), but when we are constructing a JWT for later signing (or just to store some overrides for some of the default claims) we won't have the tokenValue, and probably not even any headers. Its builder can also only be constructed if you provide a tokenValue for it (although at that point it can be null, it'll only throw when you build it in that case).
Sounds good
OK, so then they should be called the same as in the spec, right? ( |
The Jwt encode(Jwt token) throws JwtException If that's what you're thinking then that is where the confusion is coming from. The return value is obvious As far as the way I see it, the I would suggest that you open up a Draft PR so we can decide on the API for
Yes |
@forgo @krajcsovszkig-ms This feature is now available via gh-9520. Closing this as duplicate. |
This PR is to address the #6881 issue of supporting OAuth 2.0 Client authentication methods, namely "client_secret_jwt" and "private_key_jwt" methods.
My understanding comes from reading the spec, but also from useful resources such as Authlete:
My use case has been to integrate with Login.gov, so my starting approach has been to add logic in order to simplify their
private_key_jwt
method, according to login.gov docsThe major difficulty and difference when comparing the
private_key_jwt
method to other examples is the overhead of:client_assertion
JWT in the token request.Further complications exist downstream on the resource server side, primarily:
private_key_jwt
with Login.gov returns something that Spring refers to as an "opaque" token, meaning, it is not in the form of a JWT as you might expect, and the configuration on the resource side is slightly different.Then it also must be recognized that a custom
@Bean OpaqueTokenIntrospector
must be setup to make another trip to the authorization server and extract user info. This implementation of course, could vary greatly between authorization servers.This last point I feel could be improved by adding concrete use-cases to the documentation that spell out the connection between the "opaque" token and "private_key_jwt" authentication method specifically.
What I've done so far:
AbstractWebClientReactiveOAuth2AccessTokenResponseClient
to handle the two new authentication methods that addclient_assertion
andclient_assertion_type
to the body of the token request. This includes some logic to create and sign the JWT using the key store configuration added above.KeyPair
directly from the builder or from config, later simplifying the ability to sign withJWSSigner
ClientAuthenticationMethod
:Where I could use some guidance:
com.auth0:java-jwt
dependency; however, for this PR directly with spring-security, I have re-written some of that logic usingcom.nimbusds.jose
available on this project's classpath -- and that re-write may require some proper testing.