Skip to content
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

Consider enabling PKCE for confidential clients #6548

Closed
jgrandja opened this issue Feb 21, 2019 · 31 comments
Closed

Consider enabling PKCE for confidential clients #6548

jgrandja opened this issue Feb 21, 2019 · 31 comments
Assignees
Labels
in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) type: enhancement A general enhancement
Milestone

Comments

@jgrandja
Copy link
Contributor

The goal of PKCE is to provide an added level of security for OAuth 2.0 public clients (utilizing the Authorization Code Grant) from an authorization code interception attack.

However, based on OAuth 2.0 Security Best Current Practice, in section 2.1.1. Authorization Code Grant:

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.

It can also be leveraged for confidential clients for an added layer of security.

Given this, we should consider adding this support. From initial research, Okta does support this scenario where a confidential client is used to authenticate with the Token Endpoint and the code_verifier is sent as a parameter.

We should conduct further research to see which other providers support this client configuration/registration.

See this comment for further details.

Related #6446

@jgrandja jgrandja added type: enhancement A general enhancement in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) labels Feb 21, 2019
@dfcoffin
Copy link

IETF RFC 8252 "OAuth 2.0 for Native Apps" modifies RFC 6749 "The OAuth 2.0 Authorization Framework" and states in Section

[Appendix A. Server Support Checklist

OAuth servers that support native apps must:

  1. Support private-use URI scheme redirect URIs. This is required
    to support mobile operating systems. See Section 7.1.

  2. Support "https" scheme redirect URIs for use with public native
    app clients. This is used by apps on advanced mobile operating
    systems that allow app-claimed "https" scheme URIs. See
    Section 7.2.

  3. Support loopback IP redirect URIs. This is required to support
    desktop operating systems. See Section 7.3.

  4. Not assume that native app clients can keep a secret. If secrets
    are distributed to multiple installs of the same native app, they
    should not be treated as confidential. See Section 8.5.

  5. Support PKCE [RFC7636]. Required to protect authorization code
    grants sent to public clients over inter-app communication
    channels. See Section 8.1](https://tools.ietf.org/html/rfc8252#section-4.1)

@jgrandja
Copy link
Contributor Author

@sdoxsee Thanks for the offer on taking on this ticket. It would be appreciated. But before we do any work here...

We should conduct further research to see which other providers support this client configuration/registration.

So when you have some spare cycles, can you research to see which other providers support this? More importantly, we should try it with the provider to ensure that a confidential client can authenticate with both it's credentials and the code_verifier.

@sdoxsee
Copy link
Contributor

sdoxsee commented Feb 28, 2019

Thanks @jgrandja. This research would be worthwhile but, in the end, I think it would only(?) confirm whether or not PKCE should be defaulted to being "on"? I would argue that its absence from the oauth2 spec itself is reason enough to keep it "off" by default. However, the ClientRegistration could have a flag "withPkce"(?) that would determine if it should be applied or not. All that to say, perhaps the research isn't a pre-requisite for beginning work on this.

What do you think?

@jgrandja
Copy link
Contributor Author

jgrandja commented Feb 28, 2019

Good point @sdoxsee. I think you're right as far as the research not being a pre-requisite to start this task. And I agree, PKCE for confidential clients should default to "off". The user has to opt-in for this added level of security (knowing that the target provider supports this). However, I'm not sure I like the setting in ClientRegistration. Let's give this some further thought on where this configuration point could go.

@sdoxsee
Copy link
Contributor

sdoxsee commented Mar 5, 2019

Hi @jgrandja. I've been giving some more though as to where the PKCE setting should go. A couple thoughts to consider:

  1. PKCE should only be an option when authorization code grant is used. Since, ClientRegistration has authorizationGrantType at the top level, I don't see where PKCE would fit on ClientRegistration
  2. PKCE should still be the default for public clients without explicitly setting it. That is, if the ClientAuthenticationMethod is set to none, then PKCE gets chosen

What do you think about having a .pkce() on the AuthorizationCodeGrantConfigurer so that it would look something like this:

public class MySecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .oauth2Client()
                 .authorizationCodeGrant()
                    .pkce()
    }
}

The method would set the pkce flag to true and pass it a second constructor on DefaultOAuth2AuthorizationRequestResolver that takes the flag. The flag will explicitly set PKCE in addition to case where it is the default when ClientAuthenticationMethod.NONE.

@jgrandja
Copy link
Contributor Author

jgrandja commented Mar 6, 2019

@sdoxsee oauth2Client().authorizationCodeGrant().pkce() seems like the right place to have this configuration point but on second look it wouldn't quite work. Let's say the user has 2x ClientRegistration and both are confidential clients with authorization_code, however, they only want PKCE enabled for one of the clients because the other client (or provider) does not support PKCE for confidential clients - therefore this setting would not allow for this. We definitely need to provide the ability to set PKCE for a confidential client per ClientRegistration.

I'm thinking that if the user wants to enable PKCE for a specific confidential ClientRegistration than they can do that using a custom OAuth2AuthorizationRequestResolver as documented in the reference. It would be a similar implementation to CustomAuthorizationRequestResolver but with the addition of what DefaultOAuth2AuthorizationRequestResolver.addPkceParameters() does.

What do you think about this approach?

@sdoxsee
Copy link
Contributor

sdoxsee commented Mar 6, 2019

@jgrandja great point about the 2x ClientRegistration. Your alternative idea sounds good. I'll give it some thought and get back to you. Thanks!

@sdoxsee
Copy link
Contributor

sdoxsee commented Mar 20, 2019

Hey @jgrandja. Sorry for the delay. I've given your idea some thought. If I understand what you're suggesting, it would be to create something like a PkceOAuth2AuthorizationRequestResolver (and a reactive version) similar to the CustomAuthorizationRequestResolver in the reference documentation.

If that's the case then, after building the OAuth2AuthorizationRequest in the resolve method via the composed DefaultOAuth2AuthorizationRequestResolver member, it would be enhanced with the extra PKCE params.

In order to determine which ClientRegistrations should use PKCE, the authorizationRequestResolver would be created with an array of pkceRegistrationIds for clients wished to be PKCE-enabled. It would use something like this:

	.oauth2Login()
		.authorizationEndpoint()
			.authorizationRequestResolver(
				new PkceOAuth2AuthorizationRequestResolver(
					clientRegistrationRepository,
					"my-pkce-registration-id-1", "my-pkce-registration-id-2", ...
				)
			);

In the resolve method of PkceOAuth2AuthorizationRequestResolver, we'd see if the current registrationId matches any of the pkceRegistrationIds--if so, enhance the OAuth2AuthorizationRequest with PKCE params.

If this works for you, there's also how those PKCE parameters should be added. The addPkceParameters method is already duplicated in DefaultOAuth2AuthorizationRequestResolver and DefaultServerOAuth2AuthorizationRequestResolver. Unless I expose this private method to the new PkceOAuth2AuthorizationRequestResolver (which I don't like), that would make it repeated in 4 places. I'm all for not over-re-using-up-front but, at this point, I'm again tempted to put this logic somewhere else where it could be re-used. I'd originally thought of putting it in the OAuth2AuthorizationRequest itself--after all, it's now been made the default behaviour for authorization_code public clients. It could go also go in some other utility class that takes a OAuth2AuthorizationRequest and adds parameters to it but I like to avoid "utility" classes. The addPkceParameters method (perhaps with different arguments) would be called from at least 4 places from 4 classes (public client reactive and servlet, and confidential client reactive and servlet).

Shall I create a PR somewhere along these lines as a starting point or do you think it would be better to think about this a little more first?

Thanks!

@jgrandja
Copy link
Contributor Author

@sdoxsee I've been giving this some further thought and I'm not sure if a new implementation of OAuth2AuthorizationRequestResolver (being PkceOAuth2AuthorizationRequestResolver) is the right approach to solve this issue. With this approach we still need to solve the reusability issue with addPkceParameters() and I don't want to duplicate this more than the 2 times we have now. Also, I'm pretty confident there will be more use cases for enhancing the Authorization Request...a recent one #6608.

I think the approach I would like to look into further is a similar pattern found in WebClient.Builder, specifically the operation Builder apply(Consumer<Builder> builderConsumer).

I like this approach as WebClient can be configured with any Consumer that takes a WebClient.Builder. This is definitely very powerful and I like that it takes the composition approach rather than the inheritance approach.

We could have something like apply(Consumer<OAuth2AuthorizationRequest.Builder> builderConsumer) in both DefaultOAuth2AuthorizationRequestResolver and DefaultServerOAuth2AuthorizationRequestResolver. This would allow us to move the addPkceParameters() and createCodeChallenge() into a new class that looks similar to:

public class PkceAuthorizationRequestBuilder implements Consumer<AuthorizationRequest.Builder> {

	@Override
	public void accept(AuthorizationRequest.Builder builder) {

	}
}

This would remove the existing duplication of addPkceParameters() and createCodeChallenge() and allow it to be reused in DefaultOAuth2AuthorizationRequestResolver and DefaultServerOAuth2AuthorizationRequestResolver as a default Consumer in both.

Now to solve this issue, a user could provide a Consumer<AuthorizationRequest.Builder> that determines if Pkce should be applied to a confidential client by inspecting the AuthorizationRequest.Builder and than simply delegating to the new PkceAuthorizationRequestBuilder.

I think some more thought needs to be put into this but I'm feeling this approach could be quite flexible.

What do you think?

@dfcoffin
Copy link

@sdoxsee @jgrandja Since the original purpose of this issue was to implement PKCE for public clients, which implements an IETF RFC, I am concerned that it is now attempting to allow the creation of an implementation that is in the "wild" and no longer supports an IETF published RFC. While this may be reasonable, it should be first brought to the attention of the IETF OAuth WG for implementation, as I can see it opening potential security issues not being considered by this request.

@jgrandja
Copy link
Contributor Author

jgrandja commented Apr 2, 2019

@dfcoffin As an FYI, we are not planning on changing the existing implementation of DefaultOAuth2AuthorizationRequestResolver that added support for PKCE via #6485.

Also, please see this comment as Okta does support PKCE for confidential clients.

The goal of this issue is to provide support for users that want PKCE enabled for confidential clients as an added layer security on top of client authentication. This will likely be an "opt-in" feature rather than a default setting. Again, there is no intention to change the existing behaviour of PKCE for public clients.

@sdoxsee
Copy link
Contributor

sdoxsee commented Apr 24, 2019

For anyone tracking this, I just wanted to provide an update. @jgrandja I like your idea. This feature is very interesting to me but not acutely necessary. I am looking for opportunities to work on it but it will be on my own free time. Thanks!

@jgrandja
Copy link
Contributor Author

No worries @sdoxsee and thanks for the update!

@ExpDev07
Copy link

ExpDev07 commented May 8, 2019

Any updates on this? I am trying to integrate Snapchat Login Kit with Spring Security OAuth2, and let me just say: it's a pain in the ass (Snapchat has the worst documentation I've ever read, maybe because it's still in development). Although not documented, the Login Kit requires the PKCE parameters (code_verifier, code_challenge, and code_challenge_method) passed.

Now, I started writing a rather large oauth2 library myself to support this (I couldn't find any Java libraries that support PKCE), and then noticed that Spring Security already had an OAuth2 library-- and so far it works great and I love it. However! I need those PKCE parameters implemented. How would I go about doing this myself? Any simple way?

Should I do something like this?, and where do I possibly go from now?

import com.nimbusds.oauth2.sdk.AuthorizationRequest;
import com.nimbusds.oauth2.sdk.pkce.CodeChallengeMethod;
import com.nimbusds.oauth2.sdk.pkce.CodeVerifier;

import java.util.function.Consumer;

public class PkceAuthorizationRequestBuilder implements Consumer<AuthorizationRequest.Builder> {

    @Override
    public void accept(AuthorizationRequest.Builder builder) {
        builder.codeChallenge(new CodeVerifier(), CodeChallengeMethod.S256);
    }

}

Thanks in advance!

@jgrandja
Copy link
Contributor Author

@ExpDev07 Please see #6485 as PKCE client support has already been implemented. I believe this is what you're looking for as PKCE is intended for public clients. This specific issue is focused around enabling PKCE for confidential clients, which is a specialized use case and it's still up for debate on whether this will be implemented.

@jgrandja jgrandja self-assigned this May 21, 2019
@beuvenar
Copy link

beuvenar commented May 22, 2019

@jgrandja We need to interact with a provider that requires PKCE for confidential clients. Note that it is recommended by specifications to use PKCE even for confidential clients (see note in section 3.1.1 of https://tools.ietf.org/html/draft-ietf-oauth-security-topics-12#section-3.1.1) so I think this should be supported by Spring Security.

@ExpDev07
Copy link

^

@sdoxsee
Copy link
Contributor

sdoxsee commented May 24, 2019

@beuvenar and @ExpDev07 I'm interested in working on this but I'm still quite busy at the moment. If either of you need this urgently and are able to work on it, I'd be happy to pass this off to you. Our thoughts to date on how to implement this are part of this issue. I'm sure @jgrandja would be happy to provide feedback on any additional ideas you may have. Otherwise and in the mean time, feel free to thumb-up the issue and I'm pretty sure we'll be able to get to it eventually.

@jgrandja jgrandja removed their assignment Jun 4, 2019
@jgrandja jgrandja self-assigned this Jul 16, 2019
@jgrandja jgrandja added this to the 5.2.0.RC1 milestone Jul 26, 2019
@jgrandja jgrandja added this to the 5.3.x milestone Nov 18, 2019
@jgrandja jgrandja removed this from the 5.3.x milestone Feb 10, 2020
@jgrandja jgrandja added this to the 5.4.x milestone Mar 11, 2020
@jgrandja jgrandja modified the milestones: 5.4.x, 5.5.x Jul 24, 2020
@raman-nbg
Copy link

Is there any update on this?

@jgrandja
Copy link
Contributor Author

jgrandja commented Feb 8, 2021

@raman-nbg No update as of yet. We've been pretty busy on some other priority tasks. We're still hoping to get this into the 5.5.0 release.

@jgrandja jgrandja modified the milestones: 5.5.x, 5.6.x Apr 9, 2021
@stefanbethke
Copy link

I'm tasked to implement an OIDC client against IBM Security Verify Access, where the operator is requiring PKCE even for confidential clients. Is there a way in 5.3 for me to customize the configuration to enable this, perhaps by implementing some custom classes?

@stefanbethke
Copy link

I've just learned that OAuth 2.1 will require PKCE for confidential clients. I would be very happy to see support for it soon: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-02.txt#section-1.9

@stefanbethke
Copy link

Using the code in this blog post I was able to add the PKCE parameter to the request without issue. This appears to be a valid workaround.

@mtbc
Copy link

mtbc commented Jun 29, 2021

We are also seeing PKCE required in an institutional deployment of PingFederate so we figured how to get the extra properties in via a CustomAuthorizationRequestResolver.

@jgrandja jgrandja modified the milestones: 5.6.x, 5.7.x Oct 20, 2021
@drahkrub
Copy link

PKCE is also required by IdentityServer4 -> demo server.

@jgrandja jgrandja modified the milestones: 5.7.x, 5.7.0-M3 Mar 11, 2022
jgrandja added a commit that referenced this issue Mar 16, 2022
@bdemers
Copy link
Contributor

bdemers commented Mar 16, 2022

Woot! Awesome @jgrandja !!

@jgrandja
Copy link
Contributor Author

@bdemers @sdoxsee This feature enhancement is now available.

The OAuth 2.1 Draft, in Section 9.8 Authorization Codes, states the following:

Clients MUST prevent injection (replay) of authorization codes into
the authorization response by attackers. To this end, using
"code_challenge" and "code_verifier" is REQUIRED for clients and
authorization servers MUST enforce their use, unless both of the
following criteria are met
:

  • The client is a confidential client.

  • In the specific deployment and the specific request, there is
    reasonable assurance for authorization server that the client
    implements the OpenID Connect "nonce" mechanism properly.

In this case, using and enforcing "code_challenge" and
"code_verifier" is still RECOMMENDED.

In a nutshell, confidential clients that perform the OpenID Connect 1.0 authorization_code flow and implement the nonce parameter are NOT required to implement PKCE, however, it is still RECOMMENDED. For all other cases, PKCE is REQUIRED for confidential clients.

NOTE: This feature enhancement is NOT enabled by default, and must be explicitly configured. The main reasons why it was implemented this way are:

  1. PKCE is NOT required for ALL confidential clients as explained in previous point.
  2. OAuth 2.1 is still in draft and not all OAuth 2 providers will enforce PKCE for confidential clients, so we need to ensure we don't break existing client applications with the introduction of this enhancement.

To leverage this enhancement and configure PKCE for confidential clients, please see the following tests for usage:

Feedback on this enhancement would be greatly appreciated before it's released in 5.7.0. Thanks!

@bdemers
Copy link
Contributor

bdemers commented Mar 22, 2022

Hey @jgrandja thanks for the pointers!

I'm a little stuck, but I think I'm going down the wrong path (I'm my own worst enemy type of problem).

I'm not sure how to build or get access to the OAuth2AuthorizationRequestResolver, I tried something like this:

@Bean
SecurityFilterChain oauth2SecurityFilterChain(HttpSecurity http) throws Exception {

    http.authorizeRequests((requests) -> requests.anyRequest().authenticated());
    http.oauth2Login();

    http.oauth2Client().authorizationCodeGrant(customizer -> {
        OAuth2AuthorizationRequestResolver resolver = new DefaultOAuth2AuthorizationRequestResolver(

            http.getSharedObject(ClientRegistrationRepository.class), // this is null, and not an available bean
            OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI);

       customizer.authorizationRequestResolver(resolver);
    });

    http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
    return http.build();
}

Am I way off course here?

@jgrandja
Copy link
Contributor Author

jgrandja commented Mar 23, 2022

@bdemers See the reference doc on Customizing the Authorization Request for an example of configuring DefaultOAuth2AuthorizationRequestResolver.

http.getSharedObject(ClientRegistrationRepository.class), // this is null, and not an available bean

ClientRegistrationRepository is a required @Bean for oauth2-client so this would fail at start up. Something is going on with your configuration.

I noticed http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt). Is this needed for your oauth2-client application? Typically Resource Servers reside in another app from Client app.

@bdemers
Copy link
Contributor

bdemers commented Mar 23, 2022

Thanks for the link @jgrandja I'll take another look!
The resource server bits are mostly some copypasta, I'll delete that line as I continue messing with it 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging a pull request may close this issue.