Securing Web Apps Using PKCE With Spring Boot
Join the DZone community and get the full member experience.Join For Free
Technology has a way of updating faster than security standards. OAuth 2.0 is the latest and greatest standard for modern applications, but it’s eight years old now! Its contributors are working on the next version as we speak, and in the meantime, they release “guidance” periodically to help developers use OAuth 2.0 with new technology.
Last year, the developers submitted two drafts of important pieces of guidance for OAuth 2.0. OAuth 2.0 Security Best Current Practices gives advice for securing modern apps with OAuth 2.0, and OAuth 2.0 for Browser-Based Apps focuses specifically on web app best practices.
This tutorial will focus on OAuth 2.0 Security Best Current Practices and will go through the practical implications the guidance has for Spring Boot with Spring Security apps.
First, let’s clarify the relationship between OAuth 2.0 and OpenID Connect. OAuth 2.0 is for delegated authorization, and OpenID Connect is for identity and sits on top of OAuth 2.0. Let’s look at these two standards and why they’re important.
In the beginning, there were siloed websites that didn’t talk to each other, and everyone was sad.
Sites like Yelp started wanting access to the contact information you had in your Google contacts. So, Yelp naturally collected your Google username and password so that it could access your contacts. You gave Yelp your permission, so this was all good, Yes? No! With your username and password, Yelp could access your email, your docs - everything you had in Google - not just your contacts. And, worse, Yelp had to store your password in a way that it could use it in plaintext and there was no standard way to revoke your consent to Yelp to access your Google account.
We needed an authorization framework that would allow you to grant access to certain information without you giving up your password. Cue OAuth.
Three revisions later, we’re at OAuth 2.0 (there was 1.0 and 1.0a before it) and all’s right with the world. Now, an application like Yelp (a Client Application) can request an Access Token from a service like Google (an Authorization Server). You (the Resource Owner) log into Google with your credentials and give your Consent to Yelp to access your contacts (and only your contacts). Access Token in hand, Yelp makes a request of the Google Contacts API (the Resource Server) and gets your contacts. Yelp never sees your password and never has access to anything more than you’ve consented to. And, you can withdraw your consent at any time.
In this new world of consent and authorization, only one thing was missing: identity. Cue OpenID Connect. OIDC is a thin layer on top of OAuth 2.0 that introduces a new type of token: the Identity Token. Encoded within these cryptographically signed tokens in JWT format, is information about the authenticated user. This opened the door to a new level of interoperability and Single SignOn.
OAuth (and by extension OIDC) use a number of defined Flows to manage the interactions between the Client App, the Authorization Server and the Resource Server. In this post, I focus on the Authorization Code Flow. This flow is meant to be kicked off from your browser and goes like this:
- Yelp wants access to your contacts. It presents a button to link your Google Contacts.
- When you click the button, you’re redirected to Google where you login with your username and password (if you’re not already logged in).
- Google shows you a screen telling you that Yelp would like read-only access to your contacts.
- Once you give your consent, Google redirects back to Yelp, via your browser, with a temporary code (called an authorization code)
- Using this code, Yelp contacts Google to trade it for an Access Token
- Google validates the code and if all checks out, issues an Access Token with limited capabilities (read-only access to your contacts) to Yelp
- Yelp then presents the Access Token to the Google Contacts API
- Google Contacts API validates the token and, if the request matches the capabilities identified by the token, returns your contact list to Yelp
Confidential clients run on a server and are under the complete control of the company that created the application. Spring Boot, .NET and Node.js are examples of confidential client type applications. Because they are run on servers and are usually behind a firewall with other safeguards, it is safe to configure a confidential client with a secret. In OAuth 2.0, this is referred to as the
Client Secret. The Authorization server issues both a
Client Id and a
Client Secret for use in the application and this is how said application authenticates itself to the Authorization Server.
Public clients run in environments that cannot be controlled by entities like a company. There are applications such as Single-Page applications (SPAs) or mobile or native applications. If a company has many users, it’s likely that some percentage of those users have compromised machines or browsers. There’s no way for the company to control this. It is not safe to store secrets in these types of applications, since they can be inspected and decompiled. In order to take advantage of the Authorization Code flow in a public client, an extension called Proof Key for Code Exchange (PKCE) is used.
PKCE was originally developed to make mobile and native applications using OAuth 2.0 more secure. Recently its use was extended to browser-based Singe-Page Apps. Now, PKCE is recommended even for confidential clients.
Examining the Authorization Code Grant section of the security best practice guidance document, it states:
Clients utilizing the authorization grant type MUST use PKCE [RFC7636] in order to (with the help of the authorization server) detect and prevent attempts to inject (replay) authorization codes into the authorization response.
That is, there’s a security advantage to using PKCE even with confidential clients that are already authenticating themselves with a client secret.
While not complete, it is looking like the next revision of OAuth will simplify the standard to require that PKCE be used in all flows that involve an end-user (outside of the Device Flow).
The latest version of Spring Security (5.2.1 as of this writing) supports OAuth 2.0 and OpenID Connect natively. It supports PKCE for public clients. It does not yet support PKCE for confidential clients. There is a pull request (written by my colleague, Brian Demers) that is expected to be incorporated into the next release. However, Spring Security is so well written and modular, that it is easy to hook into PKCE with a confidential client today to take advantage of the recommended best security practices.
Okta’s own Spring Boot Starter makes it very easy to get started.
You can find the full source code this post here or head on over to start.spring.io to quickly create a Spring Boot app with everything you need for a confidential client. The only starters you need are: Spring Web, Okta and Thymeleaf. Okta automatically brings in Spring Security. Thymeleaf is used for html templates. The core of your
pom.xml should look something like this:
NOTE: The example code uses Java 11.
The Okta Spring Boot starter requires only three properties:
There’s an additional property used in this app for controlling whether or not PKCE will be used:
For the purposes of demonstrating the app, you can also set the logging level for the web client to
DEBUG so that you can see what the POST looks like when it exchanges the authorization code for tokens. That looks like this:
Using your free Heroku account, you can easily deploy the application by clicking this button:
NOTE: Clicking the deploy to Heroku button will allocate an Okta org for you, create an Okta OpenID Connect web application, deploy the example application and set all the environment variables for the application to run. Watch this video to learn more about the Okta Heroku Add-On.
NOTE: It’s also useful to have the Heroku command line tool installed, if you don’t already have it.
By default, the application is set to run without using PKCE. Let’s see that in action first. A user was created as part of the Okta org creation process. To see the username and password that was automatically generated for you, use the following command:
peaceful-citadel-41978 with whatever you named the app)
You’ll see output like this:
Now, you can navigate to the application (incognito window recommended):
https://peaceful-citadel-41978.herokuapp.com. When you click the Profile button, you’ll be redirected to your newly created Okta org. Enter the username and password using the
OKTA_ADMIN_PASSWORD values you saw from the config output above to login. At this point you need to set a security question for the user account as this is the first login. This is a one-time operation.
After you login, you’re then redirected back to the app and you’ll see your profile information.
To see some of what was happening internally, take a look at the application logs with this command:
Close to the bottom of the output, you should see logging information about the
POST request to the token endpoint:
Notice that it has
redirect_uri parameters. This is part of the regular authorization code flow.
Once you’ve gotten this far, the application is basically working as expected. Next, you’ll update an environment variable to make the application use PKCE. Run the following:
The application will restart. You can revisit the logs and keep the log output running by issuing the following command:
In a new incognito window, navigate back to the application and login as before. This time, the log output for the token exchange looks like this:
Notice that this time, the output includes a
code_verifier parameter. This indicates that PKCE was used in the initial authorization step and is being used in the token step as well.
Next, we’ll examine the code that makes this all work.
The good news for us is that most of what we need to support PKCE is already built into Spring Security. The only issue is that Spring Security doesn’t currently support PKCE for confidential clients. We can remedy this by using the existing architecture to change the default behavior. First, we need a custom authorization request resolver.
createHash methods are borrowed from the existing
org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver found in the current version of the Spring Security source code.
customAuthorizationRequest method is where the action is. Assuming the
OAuth2AuthorizationRequest parameter is not null, the code:
- grabs any existing attributes and additional parameters maps from the request
- adds the required pkce attributes and additional parameters to the existing maps for each
- builds and returns a new
OAuth2AuthorizationRequestthat includes the pkce attributes and additional parameter maps.
In essentially four lines of code, we alter the original authorization request to include PKCE parameters.
You can monitor the authorization request in your browser developer tools. The request will look something like this (newlines added for readability):
code_challenge_method parameters are the query string parameters added by our
addPkceParameters method above. The ordinary authorization code flow does not include these additional parameters.
The nice thing here is that once you’ve added the PKCE paramters on the authorization step, Spring Security automatically includes the
code_verifier on the token step without any additional code required.
There’s one bit of housekeeping that needs to be done to tie this all together. We need to tell Spring Security to use the
CustomAuthorizationRequestResolver. We do this with a security configuration.
Take a look at
The first part of the
configure method is standard Spring Security configuration. Here, we express that the home page (
/) and anything under the static
img folder does not require authentication. Any other path will require authentication.
The second part of the
configure method checks for the
okta.oauth2.pkce-always environment variable and if set, configures Spring Security’s
authorizationRequestResolver with our
With the security configuration in place, we can now ensure that the application uses PKCE for OAuth 2.0, thus enhancing the overall security of the application.
In the very near future, once the spring-security#7804 pull request is merged and a new version of Spring Security is released (as well as the new version of the Spring Boot Spring Security Starter), you won’t need to use the custom authorization request resolver and the security configuration as shown above. PKCE with confidential clients will be the default behavior.
This is aligned with the current security best practices as outlined in the Authorization Code Grant section.
To see the two OAuth 2.0 best practices guidance specifications referenced in this post, use these links:
For more on OAuth 2.0 and OpenID Connect, I recommend these blog posts and videos:
- OAuth 2.0 Java Guide: Secure Your App in 5 Minutes
- Use Okta Token Hooks to Supercharge OpenID Connect
- An Illustrated Guide to OAuth and OpenID Connect
- What’s going on with the OAuth 2.0 Implicit Flow?
- OAuth 2.0 Access Tokens explained
- OAuth 2.0 and OpenID Connect (in plain English)
For more on PKCE, I recommend a previous PKCE post I wrote and our documentation:
If you like this blog post and want to see more like it, follow @oktadev on Twitter, subscribe to our YouTube channel, or follow us on LinkedIn. As always, please leave a comment below if you have any questions.
Published at DZone with permission of Micah Silverman, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.