OAuth 2: Pattern to Keep access_tokens Inside a Secured Zone at All Times
Learn how to keep your tokens secure!
Join the DZone community and get the full member experience.Join For Free
OAuth 2 has become a popular protocol to follow for authorizing users to access protected resources. A typical OAuth 2 scenario:
- A user requests access to a protected resource managed by a resource server via a User Agent, typically the user's browser.
- The resource server redirects the user via an HTTP redirect to an Authorization Endpoint managed by an Authorization Server that has an account for that user. The location of the redirect response has a field typically called redirect_uri.
- The authorization server prompts the user for credentials to verify the user's identity. It might also confirm that the user is OK with allowing the user agent access to the resource held at the resource server.
- The user enters credentials and confirms allowing access if required.
- The authorization server verifies user credentials and sends an authorization_code value at the redirect_uri.
- A service running at the redirect_uri sends POST request to a Token Endpoint managed by the authorization server with authorization_code.
- Token endpoint validates the authorization_code, generates an access_token, and sends it back to redirect_uri in response.
- Service at redirect_uri retries the initial HTTP request adding access_token as a query string parameter.
- The resource server validates access-token and allows access to the protected resource.
Often in a single page application (SPA), we see that the UX layer is responsible for both the GET call to the authorization code endpoint as well as the POST call to the access token endpoint to exchange the authorization code with the access token. The SPA then uses this token to make back end, typically REST calls to the resource server.
While this approach works, it leads to a number of problems:
- Separation of concern as advised by OAuth 2 is not enforced.
- This leads to a security risk in two ways:
- The same component gets to know both the code and the token; the token becomes visible in for example Developer Tools.
- Sensitive information is typically protected behind an enterprise firewall/DMZ. In this approach, the token leaves the protected zone and lands in the open internet.
- The UX layer becomes more and more involved with bookkeeping concerns like token caching, renewing tokens by using refresh tokens, and others.
Add a server-side component to get the authorization code and exchange it for an access token. Store the access token in an internal data structure. For an incoming request from SPA, add the access token to the header before sending it to the protected resource.
- Meets the spirit of OAuth 2 authorization code: front channel and back channel communication paths kept separate.
- More secure as token never leaves the server side (DMZ).
- Aligns well with microservices-based solutions. We can design a microservice to handle the back channel communication without involving UX. The usual benefits of microservices architecture like granularity, scalability, fault tolerance, any additional security can be gained.
- More robust, as the token does not have to depend on browser sessions, storage, etc. Will work across browsers.
More Upfront Work
This requires a server-side component to be in place; while most modern application architecture is based on having a service layer in place if the application is only a front end making calls to a data layer then the architecture needs to be enhanced by bringing in an additional service layer. Again a microservices like architecture will help in this case.
Potential Conflict With Stateless Microservices
One of the key principles of a microservice-based architecture is to make your services stateless. That is, the service does not store any information that can be used across service invocations. This has a number of benefits such as seamless load balancing, easy scalability, fault tolerance, among others. However, this principle is broken if your service stores the token in a data structure such as a map inside your service instance.
A solution is to store any state, access_token in our case to a backing store such as Redis cache. The service can still maintain a private map.
Dealing With Token Expiration
When the access token expires, the back-end resource server will reject the call, returning a message. Our service can use the refresh token to get a new access token followed by putting this new value in cache — both local and backing.
One advantage of this approach is that all this can happen transparently to the user.
Disclaimer: this code illustrates at a high-level the OAuth flow as described in this article; it is not meant to be of production quality code.
Note: this example uses Spring Boot, Java 1.8, Maven 3.6 on Windows; but the artifacts can be translated to the platform of your choice.
1. Go to start.spring.io and create a Spring Boot project as below.
2. Extract the ZIP into a folder of your choice.
3. Add a folder controller under src\main\java\oauth2\demo.
4. Add a class OAuth2DemoController.java there.
5. Add the sample OAuth properties in resources\application.properties.
6. Build the Spring Boot app.
7. Run the app.
8. Send a GET request from the browser to the auth_code endpoint. Note that you need to specify the endpoint that will get the auth_code and make the POST call to the access_token endpoint as the redirect_uri. In this case, we have not written any such endpoint so the browser will not be able to redirect there, but the code will be seen in the browser box.
9. Use a tool like Postman to send a POST request to the access_token endpoint with the above code.
You should see the access_token and the refresh_token in response. This example simply returns a comma separated string as the response.
10. From your browser or from Postman, make a GET request to the business method.
You should see the "Business data" in response. Note that the GET request does not have to pass access_token, though, in practice, it will have to pass other details like the client id. The method proxyBusinessMethod received the GET request and, in turn, read access_token from the cache and used that to call the "actual" business method.
Opinions expressed by DZone contributors are their own.