-
Notifications
You must be signed in to change notification settings - Fork 5.9k
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
Add client support for PKCE #6485
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...main/java/org/springframework/security/crypto/keygen/PkceCodeVerifierStringKeyGenerator.java
Outdated
Show resolved
Hide resolved
samples/boot/oauth2webclient/src/main/resources/application.yml
Outdated
Show resolved
Hide resolved
.../main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequest.java
Outdated
Show resolved
Hide resolved
...main/java/org/springframework/security/crypto/keygen/PkceCodeVerifierStringKeyGenerator.java
Outdated
Show resolved
Hide resolved
@@ -105,6 +109,9 @@ private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String re | |||
OAuth2AuthorizationRequest.Builder builder; | |||
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) { | |||
builder = OAuth2AuthorizationRequest.authorizationCode(); | |||
if (isEmpty(clientRegistration.getClientSecret())) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this the best way to determine if PKCE should be used? This seems implicit rather than explicit which can lead to problems later on (if there are other reasons the client secret can be empty). Additionally implicit options in security tends to lead to security vulnerabilities.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this makes sense. If AuthorizationGrantType.AUTHORIZATION_CODE
and ClientAuthenticationMethod.NONE
(public client) than PKCE parameters can be applied.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @jgrandja and @rwinch, I received an email from someone watching this PR that raises a good point. Attaching below:
Hi Stephen,
Recently I bumped into your pullrequest for PKCE.
For our project we even have the requirement of using PKCE for normal web applications as an extra hardening.
Okta has put a demo of the PKCE in their playground:
https://oauth.com/playground/authorization-code-with-pkce.html
You will see that in the final step, to get the token, they pass both the client_secret (client authentication client_secret_basic in this case) and code_verifier.
In the pull request I saw some discussion on how to determine PKCE:
In our case or the above mentioned example on Okta this would not be fine. I believe we need a special parameter where we can force PKCE.
What do you think?
I think he makes a good point but it would mean some kind of further flag(?) on ClientRegistration like "withPkce" (true/false) that would only apply for authorization_code grant type. This way PKCE could be used with or without a client secret.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sdoxsee I'm not sure I'm following the thread in the email you received.
PKCE is meant to be used for Public Clients leveraging the Authorization Code grant. Public Clients are not assigned full credentials (no client secret). So I guess I'm confused with what the user is saying.
Looking at the Okta developer guide for Authorization Code Flow with PKCE, you will see that the client authentication is not required (so no client_secret
).
Important: Unlike the regular Authorization Code Flow, this call does not require the Authorization header with the client ID and secret. This is why this version of the Authorization Code flow is appropriate for native apps.
And for the standard Authorization Code Flow, you will see that the client authentication is required as this flow is targeted for confidential clients.
Important: The call to the /token endpoint requires authentication. In this case, it is a Basic Auth digest of the client ID and secret. You can find the client ID and secret in your application’s General tab. This requirement is why this call is only appropriate for applications that can guarantee the secrecy of the client secret. For more on Basic Auth, please see Token Authentication Methods.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the explanation @sdoxsee. Now I understand the use case. However, this seems more of an edge case to me. I'd prefer that we leave this out of this PR so we can get this merged. Can you please add a new ticket so we can gather more information.
the person who brought this up is saying that they have this requirement to add PKCE to a confidential client
Can you also please have the user respond to the new ticket with more detail and specifically which Authorization Server supports PKCE for a confidential client (Okta?). A link to server docs would be even better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi,
The current best practices for OAuth security specifies that PKCE should also be used for webapps:
Note: although PKCE so far was recommended as a mechanism to protect
native apps, this advice applies to all kinds of OAuth clients,
including web applications.
see: https://tools.ietf.org/html/draft-ietf-oauth-security-topics-11#section-2.1.1
Now that you are implementing PKCE you better foresee that PKCE can also be used for web applications.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vanwobe Thanks for the reference. Just below the reference it continues with...
Authorization servers MUST bind authorization codes to a certain
client and authenticate it using an appropriate mechanism (e.g.
client credentials OR PKCE).
The statement below is a bit misleading from the previous as it states client credentials OR PKCE (not both).
Either way, I'm all for making things more secure. I'm very curious though which OAuth 2.0 Provider's have implemented this recommended best practice? Can you point me to a provider that supports PKCE for a confidential client (in a web app)? A link to the reference documentation would be helpful.
Our primary goal when building out new features is to be spec compliant first and foremost and to provide out-of-the-box support for extension features supported by popular OAuth 2.0 Providers. As well, to provide extension points for users to implement for edge-case scenarios.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have it implemented in our custom provider but that aside, did you see the example from oauth.com: https://oauth.com/playground/authorization-code-with-pkce.html
This exactly demonstrates the use of PKCE in a web application.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...ework/security/oauth2/client/web/server/DefaultServerOAuth2AuthorizationRequestResolver.java
Outdated
Show resolved
Hide resolved
.../main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequest.java
Outdated
Show resolved
Hide resolved
...re/src/main/java/org/springframework/security/oauth2/core/endpoint/OAuth2ParameterNames.java
Outdated
Show resolved
Hide resolved
I've force-pushed a new commit that tries to take @rwinch's suggestions into consideration. As for explicit detection of PKCE rather that detecting the absence of a I've extended the OAuth2AuthorizationRequest class to handle PKCE specifics. The OAuth2AuthorizationRequest class needed tweaks to allow it to be subclassed as well as its Builder. I didn't subclass OAuth2AuthorizationCodeGrantRequestEntityConverter so I used an "instanceof" to be sure I could access the codeVerifier on the OAuth2AuthorizationRequest :/ I plan on writing tests once the design is a little more settled. Thanks for the feedback! Also want to acknowledge @kbolduc for pairing with me on this! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR @sdoxsee. See comments below.
...main/java/org/springframework/security/crypto/keygen/PkceCodeVerifierStringKeyGenerator.java
Outdated
Show resolved
Hide resolved
...-core/src/main/java/org/springframework/security/oauth2/core/ClientAuthenticationMethod.java
Show resolved
Hide resolved
.../main/java/org/springframework/security/oauth2/core/endpoint/OAuth2AuthorizationRequest.java
Outdated
Show resolved
Hide resolved
...java/org/springframework/security/oauth2/core/pkce/endpoint/CodeChallengeTransformation.java
Outdated
Show resolved
Hide resolved
...a/org/springframework/security/oauth2/core/pkce/endpoint/PkceOAuth2AuthorizationRequest.java
Outdated
Show resolved
Hide resolved
...src/main/java/org/springframework/security/oauth2/core/pkce/endpoint/PkceParameterNames.java
Outdated
Show resolved
Hide resolved
...rg/springframework/security/oauth2/core/pkce/endpoint/Sha256CodeChallengeTransformation.java
Outdated
Show resolved
Hide resolved
...src/main/java/org/springframework/security/oauth2/core/pkce/endpoint/PkceParameterNames.java
Outdated
Show resolved
Hide resolved
I've added some tests and fixed the bad checkstyle from the previous commit. When you get a chance, please let me know how to proceed. Thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the updates @sdoxsee! I've added a few more comments.
...rg/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolver.java
Outdated
Show resolved
Hide resolved
...rg/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolver.java
Outdated
Show resolved
Hide resolved
...rg/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolver.java
Outdated
Show resolved
Hide resolved
...rg/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolver.java
Show resolved
Hide resolved
...rg/springframework/security/oauth2/client/web/DefaultOAuth2AuthorizationRequestResolver.java
Show resolved
Hide resolved
...security/oauth2/client/endpoint/OAuth2AuthorizationCodeGrantRequestEntityConverterTests.java
Show resolved
Hide resolved
...c/main/java/org/springframework/security/oauth2/client/registration/ClientRegistrations.java
Show resolved
Hide resolved
...st/java/org/springframework/security/oauth2/client/registration/TestClientRegistrations.java
Outdated
Show resolved
Hide resolved
...-core/src/main/java/org/springframework/security/oauth2/core/ClientAuthenticationMethod.java
Show resolved
Hide resolved
...src/main/java/org/springframework/security/oauth2/core/pkce/endpoint/PkceParameterNames.java
Outdated
Show resolved
Hide resolved
@jgrandja Just pushed another commit to address your latest suggestions. Tests pass locally--hopefully Travis will agree. Thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the updates @sdoxsee. We're almost ready to get this merged! See my comments for minor updates.
Also, we need to update WebClientReactiveAuthorizationCodeTokenResponseClient
to support a PKCE token request.
Thanks!
.../security/oauth2/client/web/server/DefaultServerOAuth2AuthorizationRequestResolverTests.java
Show resolved
Hide resolved
...c/main/java/org/springframework/security/oauth2/client/registration/ClientRegistrations.java
Show resolved
Hide resolved
...st/java/org/springframework/security/oauth2/client/registration/ClientRegistrationsTest.java
Show resolved
Hide resolved
...st/java/org/springframework/security/oauth2/client/registration/ClientRegistrationsTest.java
Outdated
Show resolved
Hide resolved
...-core/src/main/java/org/springframework/security/oauth2/core/ClientAuthenticationMethod.java
Show resolved
Hide resolved
.../src/test/java/org/springframework/security/oauth2/core/ClientAuthenticationMethodTests.java
Show resolved
Hide resolved
.../src/test/java/org/springframework/security/oauth2/core/ClientAuthenticationMethodTests.java
Outdated
Show resolved
Hide resolved
Hi @jgrandja. I've pushed the changes you recommended. I added support for PKCE token request to WebClientReactiveAuthorizationCodeTokenResponseClient. As I did that, I found some differences in logic compared to OAuth2AuthorizationCodeGrantRequestEntityConverter that was adding REDIRECT_URI regardless of whether it was null or not (and it is optional for authorization_code). I now only add it if it has a non-null value. I didn't add any tests for WebClientReactiveAuthorizationCodeTokenResponseClient since the test class was only looking at responses so far. I can certainly add tests to inspect the request before it is sent but that was starting to balloon a bit. Please let me know and I'll add some tests (and for the things I mention in the next paragraph). Also in WebClientReactiveAuthorizationCodeTokenResponseClient and OAuth2AuthorizationCodeGrantRequestEntityConverter, I add the
|
Hi @sdoxsee. If you look at the first paragraph of the spec <https://tools.ietf.org/html/rfc6749#section-3.2.1> you quoted, it says:
Confidential clients or other clients issued client credentials (e.g., ClientID and secret) MUST
Authenticate with the authorization server as described in Section 2.3 <https://tools.ietf.org/html/rfc6749#section-2.3> when making requests
to the token endpoint.
Section 2.3 Client Authentication states:
If the client type is confidential, the client and authorization
server establish a client authentication method suitable for the
security requirements of the authorization server. The authorization
server MAY accept any form of client authentication meeting its
security requirements.
Confidential clients are typically issued (or establish) a set of
client credentials used for authenticating with the authorization
server (e.g., password, public/private key pair).
The authorization server MAY establish a client authentication method
with public clients. However, the authorization server MUST NOT rely
on public client authentication for the purpose of identifying the
client.
The client MUST NOT use more than one authentication method in each
request.
I believe, based on the last paragraph of Section 2.3, inclusion of the ClientID in both the Authorization
Header and in the body is a violation of the standard.
Just my 2 cents.
Best regards,
Don
Donald F. Coffin
Founder/CTO
REMI Networks
2335 Dunwoody Xing #E
Dunwoody, GA 30338-8221
Phone: (949) 636-8571
Email: donald.coffin@reminetworks.com <mailto:donald.coffin@reminetworks.com>
From: Stephen Doxsee <notifications@github.com>
Sent: February 21, 2019 09:31 PM
To: spring-projects/spring-security <spring-security@noreply.github.com>
Cc: Subscribed <subscribed@noreply.github.com>
Subject: Re: [spring-projects/spring-security] Add client support for PKCE (#6485)
Thanks for the updates @sdoxsee <https://github.com/sdoxsee> . We're almost ready to get this merged! See my comments for minor updates.
Also, we need to update WebClientReactiveAuthorizationCodeTokenResponseClient to support a PKCE token request.
Thanks!
Hi @jgrandja <https://github.com/jgrandja> . I've pushed the changes you recommended. I added support for PKCE token request to WebClientReactiveAuthorizationCodeTokenResponseClient. As I did that, I found some differences in logic compared to OAuth2AuthorizationCodeGrantRequestEntityConverter that was adding REDIRECT_URI regardless of whether it was null or not (and it is optional for authorization_code). I now only add it if it has a non-null value. I didn't add any tests for WebClientReactiveAuthorizationCodeTokenResponseClient since the test class was only looking at responses so far. I can certainly add tests to inspect the request before it is sent but that was starting to balloon a bit. Please let me know and I'll add some tests (and for the things I mention in the next paragraph).
Also in WebClientReactiveAuthorizationCodeTokenResponseClient and OAuth2AuthorizationCodeGrantRequestEntityConverter, I add the client_id for any ClientAuthenticationMethod other than BASIC because the spec <https://tools.ietf.org/html/rfc6749#section-3.2.1> says:
In the "authorization_code" "grant_type" request to the token endpoint, an
unauthenticated client MUST send its "client_id" to prevent itself
from inadvertently accepting a code intended for a client with a
different "client_id".
where I assume "unauthenticated client** must mean a public client (ie. NONE)...but client_id must also appear for POST because it isn't coming in the Authorization: Basic header. You could argue that we should always send it since we always have it and it IS optional or that I should explicitly list off NONE and POST as the condition where client_id should be included. Anyway, sorry to open another can of worms but glad you pushed off #6548 <#6548> so we can get this one out soon--hopefully :)
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub <#6485 (comment)> , or mute the thread <https://github.com/notifications/unsubscribe-auth/ABvrQ-BPsc3cidYYuizT_BJDfy8nE_L5ks5vP1Z5gaJpZM4aWmPz> . <https://github.com/notifications/beacon/ABvrQ5I0pYZqyoJAdtPDI8q6P93vV_4oks5vP1Z5gaJpZM4aWmPz.gif>
|
@dfcoffin Thanks for pointing out the following statement in 2.3. Client Authentication
Okta does support client authentication via credentials as well as |
Thanks @jgrandja and @dfcoffin. Specs are fun eh? ...sigh. Haha. Thanks for hashing out the details! @jgrandja I see your point regarding @dfcoffin. Regarding where the spec says
you say,
I believe that including the What do you think? Thanks! |
@sdoxsee The reason I pointed out that the last paragraph in Section 2.3 will be violated if the Therefore, if |
Thanks @dfcoffin
I'm not able to find anywhere in either 3.2.1 or 2.3 where it says that "any client issued a secret MUST supply a
and then goes on to describe other methods of authenticating in 2.3.2. No? Regards, |
@sdoxsee @jgrandja I agree Section 2.3.1 Client Password's title is a bit misleading, but it describes how clients assigned a
It then states:
I agree this section is rather vague, but given the example of using |
@sdoxsee Can you please add a test for Also, one more change I think makes sense. In I appreciate your patience with all this back and forth. I think this is it and we can merge. Thanks again for your efforts here. |
Thanks @jgrandja. I'll probably add 1 test to As for
Do you think that contradicts @rwinch's earlier comment by making this detection "implicit" rather than "explicit"? However, I agree with what you say since #6548 will mean that PKCE can be used with a secret as well--only that the Also, what should be done with the defaulted Thanks, |
I think this is all the logic we need in // this.clientAuthenticationMethod is initialized to ClientAuthenticationMethod.BASIC
clientRegistration.clientAuthenticationMethod = this.clientAuthenticationMethod;
// Override for this case
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(this.authorizationGrantType) &&
StringUtils.isEmpty(this.clientSecret)) {
clientRegistration.clientAuthenticationMethod = ClientAuthenticationMethod.NONE;
} Makes sense? |
Yes @jgrandja. Thanks, almost wrote the exact same but with |
@jgrandja pushed but not sure what's wrong with the build :/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -288,4 +294,52 @@ public void setCustomWebClientThenCustomWebClientIsUsed() { | |||
|
|||
verify(customClient, atLeastOnce()).post(); | |||
} | |||
|
|||
private OAuth2AuthorizationCodeGrantRequest pkceAuthorizationCodeGrantRequest() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move this method below the associated test.
@@ -500,6 +500,11 @@ private ClientRegistration create() { | |||
clientRegistration.clientId = this.clientId; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update copyright header.
@@ -174,6 +174,41 @@ public void buildWhenAuthorizationCodeGrantClientAuthenticationMethodNotProvided | |||
assertThat(clientRegistration.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.BASIC); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update copyright header.
- Support has been added for "RFC7636: Proof Key for Code Exchange by OAuth Public Clients" (PKCE, pronounced "pixy") to mitigate against attacks targeting the interception of the authorization code - PkceParameterNames was added for the 3 additional parameters used by PKCE (i.e. code_verifier, code_challenge, and code_challenge_method) - Default code_verifier length has been set to 128 characters--the maximum allowed by RFC7636 - ClientAuthenticationMethod.NONE was added to allow clients to request tokens without providing a client secret Fixes spring-projectsgh-6446
Any documentation on how to use this in a Spring app? How would I setup the configuration? |
I'd like to see an example on how to use this as well, especially with password grant. |
This is a PR for #6446 that involves simple extensions to existing spring security that would probably be used by spring desktop apps (swing, javafx, etc.). It is a WIP but wanted to get some early feedback. I made some tweaks to the sample app oauth2webclient just to illustrate how this can be used with spring boot. I omitted the
client-secret
and addedclient-authentication-method: none
. Thanks!Took the discussion in #5652 into account.
Here's a brief overview of the use-cases for client-side PKCE as it pertains to spring security (ref #6446):
Native/Mobile
Of course, only JVM-based Mobile OS's (Android). Android uses a "Custom URI scheme" that is registered with the OS to capture the
code
. I'd imagine that you'd probably use a library like https://github.com/openid/AppAuth-Android rather than spring security? It appears there was an effort at one time to connect Spring and Android (https://github.com/spring-projects/spring-android) but the project now seems quite stale (dead?). In theory, you could probably have a webserver embedded in your native app that, if it used spring security, could handle the code callback but that seems heavy to me.Bottom line: I don't think this is the target use-case for spring security client PKCE support but could be used in theory.
Desktop
JVM desktop apps with spring exist (we have one!). We'd need some sort of loopback webserver to capture the authorization
code
. This could be a very simple one that spins up, on demand, serves a certain port and then shuts down--but that requires custom code (and could be done at a later time). If I simply make our non-web app a web one (by commenting outmain.web-application-type: none
), we'd get a tomcat and spring security auto configuration just like other web applications. The difference here is that the app needs to be delivered to the users own machines--thereby making the client_secret unviable and PKCE a good fit. Turning non-web apps into web ones feels a bit heavy but simple.I think using refresh tokens in spring security clients is safe because it's in memory rather than disk?
Bottom line: Simpler is better, desktops have the resources for a local webserver. Why not leverage spring-boot's embedded server? Any other lighter-weight suggestions?
Client-side browser-based JavaScript clients (SPA)
These authorization request are typically all javascript-initiated. The authorization
code
comes back to client and client makes the token call itself. I figure that spring security is necessary. Question: Why are implicit calls created in spring security (e.g. OAuth2AuthorizationRequest.implicit())?Bottom line: Why is spring supporting this category at all? We probably don't need PKCE support.