OAuth Bad Practices: Token Storage & Cookies
— OAUTH, cookies, openid connect — 3 min read
An interesting read for the security minded identity professional is OAuth 2.0 for Browser Based Apps. It’s fun because it discusses real-world cyber risks when implementing OAuth 2.0. It’s been designed as a collection of architectural patterns for implementing OAuth or OpenID Connect in browser applications but has a strong lean on strong security principles.
An area of the specification we’re going to look at is Token Storage. There are a number of different ways access, refresh and/or id tokens can be stored and used by browser applications. Whichever mechanism you use could have significant impacts on the security posture of your application.
Often in our role when enabling OAuth for applications, there is a disconnect between the Identity team and the application developers. It should really be part of our job to consult and advise developers on how best to implement OAuth. That includes how to store tokens.
This specification actively recommends against cookies that are JavaScript accessible. From an implementation perspective, it’s one of the easier mechanisms to get started with as cookies are one of the original storage types in browsers. So it's understandable why developers might default to this storage type. You receive an access token from an Authorization Server, set a cookie with the token and access that cookie value when you want to make a call to a resource server. However, there are two main flaws that we can see with this model.
- Malicious XSS can easily access JavaScript Cookies.
- When the cookie is set, it will automatically send the cookie in requests to the domain where the web application was loaded. This could have unintended consequences if the access token is only to be used with a specific resource server on a different domain.
The following diagram illustrates how the browser loads the OAuth client from the Web Host (example.com), it will receive an access token from the Authorization Server after performing an Authorization Code grant. The client will then store the access token in a cookie as a storage mechanism. It will make an API call to the Resource Server, extracting the access token from the cookie and injecting it into the request’s Authorization header.
The unintended consequence of this is if the browser is refreshed, or navigates to another page on the Web Host (example.com), by design, the cookie (containing the access token) will be sent to the Web Host as well. This could lead to leakage of tokens in web logs or other compromised pages on the Web Host itself.
A second attack is Cross-Site Scripting (XSS). This involves injecting JavaScript into a web page giving you access to cookies that are set by JavaScript. Once the malicious code accesses the raw access token in the cookie, it needs to exfiltrate the token. This could be done through a browser redirect, or if the website isn’t secured properly, an API call which is invisible to the attack target.
Ideally the mechanism that is used to store cookies should be resistant to XSS attacks, and also to exfiltration. An attacker has two main jobs, one is to get malicious code to get its hands on your session tokens, and the second is to send that token somehow to the attacker who can begin impersonating you.
Code example
To help demonstrate what we've discussed, an example of how the XSS attack works has been published on our github, here: https://github.com/IdentityCitizen/OAuth-Browser-Token-Protection/blob/main/cookies.html
Conclusion
Avoid using JavaScript cookies as a storage mechanism for your tokens.