Secure OAuth 2.0: How To Keep OAuth Secure?
Previous parts (part 1, part 2) of the series introduced the risks and described potential vulnerabilities in OAuth 2.0 implementation. This section is the crème de la crème as it is a checklist of secure OAuth 2.0. If you follow these tips, you decrease the risk dramatically.
In short, to keep OAuth secure you should consider 5 following steps which I describe in more detail later in the article:
- Use OpenID Connect for authentication
- Choose correct grant type
- Harden delivery of the access token
- Store the access token in safe place
- Configure the access token securely
Use OpenID Connect for authentication
As mentioned many times before — OAuth is not for authentication. However, if you need to build a Single Sign On mechanism and already have the OAuth 2.0 implemented there is a simple solution — use OpenID Connect.
“OpenID Connect 1.0 is a simple identity layer on top of the OAuth 2.0 protocol. It allows Clients to verify the identity of the End-User based on the authentication performed by an Authorization Server, as well as to obtain basic profile information about the End-User in an interoperable and REST-like manner.”
(Identity, Authentication) + OAuth 2.0 = OpenID Connect
In simple words, OpenID Connect adds verifiable assertions about the identity of signed-in users. More information about OpenID Connect and how to integrate it can be found here.
Choose correct grant type
In most cases only the authorization code type should be used because, in comparison to the implicit type, it does not leak the access token in the URL (that could be saved in history or leaked with referrer header), it is not vulnerable to token injection and the access token request could be sent on the backend side.
The case of Single Page Application is not an exception. Even though the access token cannot be retrieved on the backend side, it is still obtained in a safer way using XML HTTP request (XHR). Moreover, it is possible to integrate different domains (or origins to be exact) thanks to Cross-Origin Resource Sharing (CORS).
The password type should be used only if the client application is trusted because it would have access to the user’s credentials. However, do not use it if you do not have to, because this enlarges the attack space.
The client credentials can be used only when the client application and authorization server are owned by the same authority.
Deliver the access token securely
Regardless of the grant type and format of the access token, its transmission from authorization server to the client application must be secured.
The OAuth implementation must verify the redirection URL to which the access token is sent. The lack of verification leads to the open redirection vulnerability which allows to take over the access token.
The easiest way to validate the redirection URL is to set one when the client application is registered and on each access token request make sure the redirection URL is exactly the same as the registered one.
It is important to remember that OAuth must compare the exact value of the submitted redirection URL and registered URL and not check whether the submitted URL only contains the registered part, because the attacker could easily create such domain (e.g. victim-comain.com.attacker-domain.com).
Defense against CSRF
It is important to secure OAuth against Cross-Site Request Forgery attacks with the state parameter, which plays the role of an anti-CSRF token.
It is a pseudo random number generated by the client and verified upon reception of the response from the authorization server, which must reply it unmodified.
Thanks to this verification the client application makes sure that the server responded to its access token request and the process of access token generation was not initiated by somebody else (e.g. an attacker of course).
Harden it with Proof Key for Code Exchange (PKCE)
The authorization code grant type is not vulnerable to access token leakage, because the token is not present in the URL. However, instead of an access token, the code is returned in a redirected response. That brings back the possible leakage attack on the code parameter instead (e.g. due to the open redirect vulnerability).
Here comes the PKCE extension which is based on the challenge-verifier scheme and makes sure that the access token is requested by the same client application that initiated the process.
The flow is following:
( A )-Client application creates a code challenge (type of challenge function is selected by the client, e.g. the hash of some unique secret) and sends it in the authorization request.
( B )-Authorization server authenticates the resource owner, saves the code challenge and returns the code.
( C )- In order to get the access token client application must send the code and prove that he initiated the flow. Therefore it sends the code verifier (e.g. unique secret from the first step) together with the code.
( D )-Authorization server verifies whether the code verifier matches the code challenge and returns the access token.
The PKCE protects your users from losing their accounts even if the code is leaked (e.g. via open redirect vulnerability).
Store the access token securely
The access token is as important as the session identifier so its security must be at the same level. One of the more important challenges for session identifiers is the secure storage. Here we include a few best security practices for access token storage.
The cookies mechanism with additional flags, including Secure and HttpOnly, is a good and secure way of storing the session identifier. It can also be used for the access tokens.
Cookies with HttpOnly flag also mitigates the risk of access token theft using Cross-Site Scripting vulnerability. Indeed, the frontend application does not have to know the token value as it would be transmitted in the Cookie header to the resource server anyway.
Of course, not all of the applications are able to store the access tokens in the cookies. Especially, the frontend-only applications cannot do that. And here comes the second best security practice.
Defend the application from XSS using Content Security Policy
Therefore it is important to protect the application from XSS using:
- the proper data encoding,
- Content Security Policy,
- the X-XSS-Protection header.
Configure the access token securely
The OAuth framework does not specify the format of an access token. Using specific format (e.g. JSON Web Token) for the access tokens allows to specify its attributes that can be checked and validated by the resource server. Using special attributes like the lifetime increases the security of tokens.
Verify access token signature
The JSON Web Token (JWT) is a compact claims representation format that uses JSON to encode the payload and its cryptographic signature.
If your OAuth implementation uses JWT for access tokens you must verify that:
- It does not use the None algorithm which basically means that no signature is generated and verified. If that is the case the attacker can easily spoof any access token (and any user), because the resource server cannot verify the signature.
- The same signature algorithm (e.g. RSA for asymmetric signature or HMAC for symmetric one) is used for both operations: signature generation and verification.
Short lived tokens
A common approach to grant access to resources is to generate two tokens: the access token used to get the access and the refresh token used to generate a new access token when the previous one expires.
It is quite popular to generate a long-lived or non-expiring access token for greater usability and development simplicity. Indeed, non-expiring tokens mean less work for the developers because they do not have to handle the refresh tokens.
However, long-lived and non-expiring tokens decrease the security for a few reasons:
- the time when the legitimate access token can be stolen is getting longer or forever,
- long-lived tokens generated for third-party applications allows them to execute the operations later when the user is offline.
Therefore, in general we recommend generating short-lived access tokens and using the refresh tokens to renew them.
You can use the long-lived or non-expiring access tokens if:
- you want to give an offline access to the resources for the third party applications,
- the scope of the token is limited to the specific resources or operations.
On the other hand, if you want to give access to sensitive resources and functions (e.g. password reset) you should not use the refresh tokens to make sure that each call is authorized.
What to do next?
Remember to download a quick ✅ checklist of best practices for OAuth 2.0 ✅ and go through it for your implementation.