In a Authorization Code Grant flow, once a public client such as a Single Page Application (SPA) obtains a OAuth 2.0 access token, where should the SPA keep it?
- Storing the access token in locale storage or session storage opens up to cross-site scripting (XSS) attacks, so that should be avoided.
- Storing the access token in a non-httpOnly cookie also opens up to XSS attacks, so that should be avoided as well.
- Storing the access token in a httpOnly cookie is not technically possible because that is the point of httpOnly.
So the only secure remaining option that I see is to keep it in memory. Is it actually secure? Is it the only secure way?
It's all about the risk you want to accept.
If you store it in a cookie, you potentially open up your application to CSRF. While it may make sense to exchange XSS for CSRF by storing the token in a httponly cookie, it doesn't make much sense to do so with a non-httponly cookie that besides CSRF is also vulnerable to XSS.
Storing it in localStorage or sessionStorage is ok in many cases. With choosing that, you accept the risk of XSS having access to tokens. To mitigate this risk, you might want to implement mitigations, like for example static security scanning with a suitable tool, regular pentesting and so on - security is not just code, it's also processes around how you create that code. With mitigations in place, you can decide to accept the residual risk.
You can also store tokens in memory, like for example in IIFEs I guess, from where it's somewhat harder to read in an XSS attack. Storing it in a plain variable doesn't help (javascript from XSS would still have access), and I'm not entirely sure about what the latest JS can do to securely make it inaccessible from outside a given object. It's probably not possible in a way that is actually secure.
Or you can go down a different route. You can store very short-lived access tokens in localStorage, accepting the risk of XSS having access. However, your IdP can issue refresh tokens in httponly cookies for the IdP domain. This way even if an access token is compromised, it is only valid for a limited amount of time, and then the attacker will not be able to renew it. This may make sense in some applications, and probably not in others.