6

I'm a security newb trying to find out how to secure my SPAs, and am totally lost in the forest of RFCs, BCPs, drafts and blog posts. If possible, I'd like to serve my SPAs statically from a cdn. At first I was uplifted by this article from Okta explaining why and how to replace the implicit flow with the code+PKCE flow. This BCP draft also seemed promising, however (emphasis mine)...

The code in the browser then initiates the authorization code flow with the PKCE extension (described in Section 7) (B) above, and obtains an access token via a POST request (C). The JavaScript app is then responsible for storing the access token securely using appropriate browser APIs.

Storing the access token securely using appropriate browser APIs – is this even possible?

If I understand it correctly, the code+PKCE flow for public clients is only better than the implicit flow because it doesn't put the access token in the address bar and history, but it still has the drawback that the tokens need to be stored in localstorage/sessionstorage/non-httponly-cookies.

Right?

So I'd still be vulnerable to XSS and evil browser extensions.

I'm considering two alternatives:

  1. Use a confidential client backend that serves my public SPA clients. Confidential client receives tokens in back-channel, and sets them as HttpOnly-cookies. Public clients can now talk to protected resources without using confidential client as proxy on every request.
  2. Storing tokens on confindential client backend so that they never reach the public clients. Public clients instead are given a session cookie, referencing the tokens. Public clients will need to use confidential client as proxy on every request to protected resources.

Storing tokens in HttpOnly-cookies seems a lot better than storing them in localstorage/sessionstorage, but comes at cost of serving SPA from confidential client instead of directly from cdn.

Any advice on these approaches? Any literature that explains these approaches (or another canoncial way) in more depth?

AleksG
  • 161
  • 3
  • Evil browser extensions can steal http-only cookies. There is nothing you can do to secure your SPA against an evil browser extension. – Conor Mancone Feb 03 '20 at 14:41
  • As Conor said, but worded slightly differently, since you never control the client you cannot secure the client from attacks. Your users may neglect to patch, use shared computers in the library and not log out or write their own open source browser as their first C++ project so its full of buffer overflows. The best you can do is use the correct security options (ie httponly flag) and hope they run an up to date well configured browser. – wireghoul Feb 03 '20 at 15:27
  • 1
    Thank you for responses. I see – we can't really protect individual users from doing silly stuff (or from being hacked in a non-silly way). But still there's a difference – an extension might be able to use your httponly session cookie to call protected resources, but at least it's not getting hold of a long-lived refresh token? – AleksG Feb 04 '20 at 07:32

2 Answers2

1

I wanted to add a comment, but I'm new here, not enough rep...

About the long-lived refresh token, you shouldn't get on with your PKCE based SPA. It should be access token and id token only (for bearer) or possibly id token only (for JWT stateless). By definition both should have a short life with no refresh possibility. Instead you should use the iframe defined in the spec of openid to "refresh" the access id tokens without the use of a refresh token.

Here's an example of an angular lib that implements the iframe logic: https://manfredsteyer.github.io/angular-oauth2-oidc/docs/additional-documentation/refreshing-a-token.html Look at the implicit flow, silent refresh logic.

If you really want to have a refresh token with a PKCE on SPA, the requirements you should meet are here: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-13 section 4.12. Be sure that the refresh token logic is correctly implemented by the auth server.

1

Auth0 recommends storing the token in JavaScript memory:

Auth0 recommends storing tokens in browser memory as the most secure option. Using Web Workers to handle the transmission and storage of tokens is the best way to protect the tokens, as Web Workers run in a separate global scope than the rest of the application.

If you cannot use Web Workers, Auth0 recommends as an alternative that you use JavaScript closures to emulate private methods.

Doing this means the token is not persisted through page refreshes, so it implies the use of Silent Authentication, in which the token is silently fetched in background after a refresh. This can be done with the prompt=none OIDC parameter for the authorization call.

J. Nuutinen
  • 111
  • 3