At the end of the OAuth Authorization Code grant, after a user presents their credentials at login, a code is returned which can be exchanged for one or more tokens at the token endpoint.
These tokens include an access token, an optional refresh token, and an optional id token. The access token is used to get access to different APIs and protected resources. The refresh token lets you mint new access tokens, and the id token is used by the client to display information about the user.
What should you do with all of these tokens? How can they be used by your application to ensure that only the correct users get access to data and functionality?
sequenceDiagram
participant User as User/Browser
participant App
participant FusionAuth as Authorization Server
User ->> App : View Initial Page<br/>Click Login
App ->> User : Redirect User To <br/>Authorization Server With Scopes
User ->> FusionAuth : Request Login Page
FusionAuth ->> User : Return Login Page
User ->> FusionAuth : Provides Credentials
FusionAuth ->> FusionAuth : Validate Credentials
FusionAuth ->> User : Redirect With Authorization Code
User ->> App : Request Redirect URI
App ->> FusionAuth : Request Tokens
FusionAuth ->> App : Return Tokens
Note over User, FusionAuth: What Happens Now?The Authorization Code grant up to the point where tokens are requested from the token endpoint.
The FusionAuth team has helped hundreds of customers integrate our auth server into their applications. There are many different ways you can choose to perform an integration, but the team recommends certain options that offer the best tradeoffs between functionality and security.
Why use the OAuth Authorization Code grant
But first, why use the Authorization Code grant at all? There are, after all, simpler ways to offload authentication. For example, with FusionAuth, you can use the Login API and pass the username and password directly from your application to FusionAuth, getting a token in return. Why bother with the OAuth dance of redirects?
When you use the Authorization Code grant, you stand on the shoulders of giants. Many people in the Internet Engineering Task Force (IETF) working group have spent lots of time refining this grant, poking and fixing holes in its security, documenting it, and building libraries on top of it. You also benefit from documents such as OAuth 2.0 for browser based apps, currently being developed, and OAuth 2.0 for native apps.
The FusionAuth team firmly believes that using standard OAuth grants to integrate a third party auth server into your application architecture allows you to leverage these benefits. It also leaves open migration possibilities, should your auth server fail to meet your needs. OpenID Connect (OIDC) is another standard which layers identity information onto OAuth grants.
When using the Authorization Code grant, in addition to the wisdom of the IETF members, you get the following benefits:
- Customer personally identifiable information (PII) is stored in one safe and secure location.
- You have one view of your customer across all your apps.
- Granular user permissions with scopes, some of which are standardized.
- Advanced authentication functionality such as MFA, enterprise single sign-on and login rate limiting can be implemented in one place for all applications.
- You can upgrade such authentication functionality without modifying downstream applications.
- You can offer single sign-on across all your custom, commercial and open source applications.
- Common login related workflows such as changing profile data or passwords can be centralized and managed by the auth server.
If you’ve decided to use the Authorization Code grant, you need to store the resulting tokens. There are two main options:
- storing them on the client
- storing them in a server-side session
Store tokens on the client
The first option is to send the access token and refresh token down to the client. While both are stored on the client, only the access token must be presented to any APIs or protected resources. The refresh token should be presented to FusionAuth, but that workflow will be covered in more detail below. If the refresh token cookie is sent to a resource server, it can be safely ignored.
When using a browser, store these as HttpOnly, secure cookies with a SameSite value of Lax or Strict.
If you choose this option, the browser, whether a simple HTML page with some JavaScript or a complicated single page application (SPA), makes requests against APIs, and the access token is along for the ride.
As long as the APIs live on a common domain, or a parent domain, the access token cookie will be sent with requests. For example, the server which gets the tokens can live at auth.example.com and if you set the cookie domain to .example.com, APIs living at api.example.com, todo.example.com, or any other host under .example.com, will receive the token.
sequenceDiagram
participant User as User/Browser
participant App
participant FusionAuth as Authorization Server
participant API1 as api.example.com
participant API2 as help.example.com
Note over User,API2: ... Proceed Through Authorization Code Grant ...
FusionAuth ->> App : Return Tokens
App ->> User : Send Tokens As HTTPOnly, Secure Cookies<br/>With a Domain of example.com
User ->> API1 : Send Access Token With Request
API1 ->> API1 : Validate Access Token
API1 ->> User : Send Data or Complete Requested Operation
User ->> API2 : Send Access Token With Request
API2 ->> API2 : Validate Access Token
API2 ->> User : Send Data or Complete Requested Operation
User ->> API1 : Send Access Token With Different Request
API1 ->> API1 : Validate Access Token
API1 ->> User : Send Data or Complete Requested OperationStoring the tokens as secure, HttpOnly cookies.
When using a native app, store these tokens in a secure location, such as the iOS Keychain or Android internal data. Retrieve them and append them to the proper header before making API requests.
Token validation
In the diagram above, there’s a Validate Tokens step. Validating the access token each time they are presented is critical to securely building your application.
In the diagram below, each API validates the token presented by the client, even if the token has been seen before, as is the case with api.example.com.
One validation approach that works if the token is signed and has internal structure, which is true of many but not all access tokens and is illustrated below. A JSON Web Token (JWT) meets these criteria, but there are formats that work as well. JWTs are used by FusionAuth and other auth servers as the access token format. This is not guaranteed by the OAuth specification.
With a signed token, the API server can validate the access token without communicating with any other system, by checking the signature and the claims.
sequenceDiagram
participant User as User/Browser
participant API1 as api.example.com
participant API2 as help.example.com
Note over User, API2: ... Tokens Have Been Stored In Cookies ...
User ->> API1 : Send Access Token As Cookies With Request
API1 ->> API1 : Validate Access Token
API1 ->> User : Send Data or Complete Requested Operation
User ->> API2 : Send Access Token As Cookies With Request
API2 ->> API2 : Validate Access Token
API2 ->> User : Send Data or Complete Requested Operation
User ->> API1 : Send Access Token As Cookies<br/>With Different Request
API1 ->> API1 : Validate Access Token
API1 ->> User : Send Data or Complete Requested OperationZooming in on token validation.
The APIs must validate:
- the signature
- the expiration time (the
expclaim) - the not valid before time (the
nbfclaim) - the audience (the
audclaim) - the issuer (the
issclaim) - any other business specific claims
All of these should be validated as soon as the request is received. They should be validated before any additional processing is done, because if any of these checks fail, the requester is unknown. At that point, the requester is at best buggy software and at worst an attacker.
The signature and standard claims checks can and should be done with a language specific open source library, such as fusionauth-jwt (Java), node-jsonwebtoken (JavaScript), or golang-jwt (golang). Checking other claims is business logic and can be handled by the API developer.
Introspection
If you don’t have a token that has internal structure and a signature, another option is to introspect the token by presenting it to FusionAuth. Here the validity of the token is confirmed by FusionAuth.
sequenceDiagram participant User as User/Browser participant App participant FusionAuth as Authorization Server participant API1 as api.example.com Note over User, API1: ... Proceed Through Authorization Code Grant ... FusionAuth ->> App : Return Tokens App ->> User : Send Tokens As HTTPOnly, Secure Cookies User ->> API1 : Send Access Token With Any API Requests API1 ->> FusionAuth : Validate Access Token Via Introspection FusionAuth ->> API1 : User Data Provided After Access Token Validated API1 ->> User : Send Data or Complete Requested Operation
Storing the tokens as secure, HttpOnly cookies and using introspection to validate them.
A successful introspection will return JSON. The claims in the JSON still need to be checked:
- the expiration time (the
expclaim) - the not valid before time (the
nbfclaim) - the audience (the
audclaim) - the issuer (the
issclaim) - any other business specific claims
Using introspection adds a dependency on FusionAuth, but removes the need for APIs to validate the token signature. Again, the claims must still be checked.
Using the Refresh Token grant
At some point the access token will expire. The client must handle any access denied error.
When you request a scope of offline_access in the initial authorization sequence, after successful authentication you will receive a refresh token as well as an access token.
sequenceDiagram participant User as User/Browser participant App participant FusionAuth as Authorization Server participant API1 as api.example.com Note over User,API1: ... Proceed Through Authorization Code Grant ... FusionAuth ->> App: Return Tokens App ->> User : Send Tokens As HTTPOnly, Secure Cookies<br/>With a Domain of example.com User ->> API1: Send Access Token With Request API1 ->> API1: Validate Access Token API1 ->> User: Send Data or Complete Requested Operation User ->> API1: Send Access Token With Request API1 ->> API1: Access Token Determined To Be Invalid API1 ->> User: Sends Access Denied Message User ->> FusionAuth: Present Refresh Token FusionAuth ->> FusionAuth: Validate Refresh Token FusionAuth ->> User : Present New Access Token User ->> API1: Send Access Token With Request API1 ->> API1: Validate Access Token API1 ->> User: Send Data or Complete Requested Operation
Using a refresh token.
After the access token expires, the client presents the refresh token to the auth server, such as FusionAuth. That server validates the user’s account is still active, that there is still an active session, and any other logic that may be required.
When the checks pass, the auth server can issue a new access token. This can be transmitted to the client. This then transparently extends the user’s session.
Benefits of client stored tokens
If you choose to use client stored tokens, you gain a lot of horizontal scalability. As long as the APIs are on a domain to which cookies can be sent, they are sent along with any request your application makes.
As mentioned above, this approach is a great fit for a single page JavaScript application using data from multiple APIs on the same domain.
Using secure HttpOnly cookies protects you from cross-site scripting (XSS) attacks. XSS is a common mechanism for attackers to gain access to tokens and therefore to make requests masquerading as another user. Secure HttpOnly cookies are not available to JavaScript running on the page, and therefore can’t be accessed by malicious scripts.
If APIs are on different domains, you have two options. You can use a proxy which can ingest the token, validate it and pass on requests to other domains, or choose the session based approach, discussed later.
Below is a diagram of using the proxy approach, where an API from todos.com is called through a proxy living at proxy.example.com. This is needed because cookies set from the .example.com domain will never be sent to the todos.com domain due to browser rules.
sequenceDiagram participant User as User/Browser participant App participant FusionAuth as Authorization Server participant Proxy as proxy.example.com participant API1 as api.example.com participant API2 as todos.com Note over User, API2: ... Proceed Through Authorization Code Grant ... FusionAuth ->> App: Return Tokens App ->> User : Send Tokens As HTTPOnly, Secure Cookies<br/>With a Domain of example.com User ->> API1: Send Access Token With Request API1 ->> API1: Validate Access Token API1 ->> User: Send Data User ->> Proxy : Send Access Token With Request Proxy ->> Proxy : Validate Access Token Proxy ->> API2: Send Access Token With Request API2 ->> API2: Validate Access Token API2 ->> Proxy: Send Data Proxy ->> User: Send Data
Using a proxy to access APIs on different domains.
Alternatives to client stored tokens for the browser
Why use browser cookies and not another storage mechanism such as memory or localstorage? Why not bind the cookie to the browser? All options have tradeoffs, and using cookies works for many customers.
Localstorage is a difficult option because, unless you also set a fingerprint cookie, as recommended by OWASP, you are exposed to XSS attacks. Remember, any JavaScript running on the page has access to localstorage. If you do follow the OWASP recommendations by adding a fingerprint to your token and sending a cookie down with a related value, you’ll be limited to sending requests to APIs on the domain to which the cookie is scoped.
If you use an in-memory storage solution, when the browser is refreshed, the token is gone. The user has to log in again, which is not a great experience.
You can also use a service worker to isolate access to the tokens. This is a secure option, but all requests from the application must then pass through the service worker.
Client binding measures, such as Distributed Proof of Possession (DPoP), remove XSS danger. The token can’t be used without a private key that only the proper client possesses. However, these approaches require additional setup on the client side and are relatively new. For example, as of this writing, DPoP is not yet an IETF standard.
But if client storage won’t meet your needs, you can use tried and true web sessions.
Using sessions
Another option is to store the access token and refresh token in the server-side session. The application uses web sessions to identify with the server, and the token is available for other requests originating server-side. This is also known as the backend for frontend (BFF) proxy.
sequenceDiagram
participant User as User/Browser
participant App
participant FusionAuth as Authorization Server
Note over User, FusionAuth: ... Proceed Through Authorization Code Grant ...
FusionAuth ->> App : Return Tokens
App ->> App : Store Tokens in Session
App ->> User : Return Session Cookie
User ->> App : Session Based Interactions
App ->> User : Data/HTML/JSONStoring the tokens server-side in a session.
If you have a valid token from the OAuth token endpoint via the app, the user is authorized. Depending on your application and how you are validating the token, you might need additional information from the token, but you might not need to send it anywhere else.
In other situations, you need to retrieve data from other APIs with no domain limits, over secure, server-side channels.
Below is an example of how you can proxy API requests through server-side components. The APIs receiving the tokens must still validate them, with the same options as discussed above.
sequenceDiagram
participant User as User/Browser
participant App
participant API1 as todos.com
participant API2 as reminders.com
Note over User, API2: ... Token Previously Stored In Session ...
User -> App: Request Todo Endpoint
App -> API1: Request Todo Endpoint<br/>Passing Token From Session<br/>Along In Header
API1 -> API1: Validate Token
API1 -> App: Send Todo Data
App -> User : Send Todo Data
User -> App: Request Reminder Endpoint
App -> API2: Request Reminder Endpoint<br/>Passing Token From Session<br/>Along In Header
API2 -> API2: Validate Token
API2 -> App: Send Reminder Data
App -> User: Send Reminder DataProxying API calls using tokens stored in a server-side session.
Even if you don’t present the token to other APIs, you still get the above benefits from using the OAuth Authorization Code grant:
- Customer personally identifiable information (PII) is stored in one safe and secure location.
- You have one view of your customer across all your apps.
- Granular user permissions with scopes, some of which are standardized.
- Advanced authentication functionality such as MFA, enterprise single sign-on and login rate limiting can be implemented in one place for all applications.
- You can upgrade such authentication functionality without modifying downstream applications.
- You can offer single sign-on across all your custom, commercial and open source applications.
- Common login related workflows such as changing profile data or passwords can be centralized and managed by the auth server.
The id token
What about the id token? That was mentioned initially as an optional token, but then not discussed further.
When you request a scope of profile in the initial authorization sequence, after successful authentication you will receive an id token as well as an access token. There are other OIDC scopes as well, beyond profile.
The id token can be safely sent to the browser and stored in localstorage or a cookie accessible to JavaScript. The id token should never be used to access protected data, but instead is for displaying information about a user such as their name.
Id tokens are guaranteed to be JWTs, so you can also validate them client side to ensure their integrity.
Summing up
The two options of client-side token storage or server-side sessions handle the majority of systems integrating with the OAuth and OIDC standards to safely authenticate and authorize users.
Client-side storage is a great choice when you have disparate APIs and want to support highly distributed clients such as mobile devices or browsers in a scalable fashion. Server-side session storage is simpler and easier to integrate into monolithic applications.
The FusionAuth team recommends using one of these two options after you obtain the token at the end of the Authorization Code grant.
Happy OAuthing!




