Search code examples
authenticationoauth-2.0oauthfrontendaccess-token

A question around the access token frontend get after authentication


When thinking the OAuth2.x grant types (e.g. code or password or implicit grant type), eventually, the javascript web frontend application will get an JWT access token as a response for the authentication request (e.g. login user) .

Then, the frontend will use above access token (during its valid period) also in bearer header of subsequent requests that are sent to backend, which means this access token will need to be stored in frontend side so that when forming the subsequent requests, it use the access token in request header. Am I making sense here?

My two questions:

Q1. isn't it unsafe to store the access token in javascript frontend? Because developer tools of the browser can unveil this access token which could be used by malicious users. Am I making sense here? If so, what is the proper way to host this access token in frontend once it gets it after authentication?

Q2. If frontend also wants to validate the token by checking the claims inside, it would mean the public key is needed in frontend, is that a fine way to go? If not, what would be the better way if frontend needs to validate the stored token when forming the subsequent requests to backend?


Solution

  • The concern in your first question is actually valid but not for the reason you stated. As Evert already mentioned, an end user has access only to their own browser storage. However, it's still quite risky to store access tokens in the browser because of XSS attacks. here are some facts

    1. Oauth 2.0 Access tokens are bearer tokens, which simply means, there is no binding between the token itself, and who it was meant for. that is, anyone who has the token can use it.
    2. When you store access tokens either in the session storage or local storage, you need to retrieve the tokens for later use in an API call
    3. Client-side storage is generally vulnerable to cross-site scripting. in this case, any bad javascript code that manages to run in the same browsing context as your good Javascript code also can have access to the stored access token. this could be for instance injected code via a malicious browser extension or a vulnerable npm module you are using as an upstream dependency in your code.

    Based on these facts, storing tokens on the client side can lead to token exfiltration. You can try to limit the potential of XSS by writing a proper content security policy (CSP) but the risk is still there. So if your application must store the tokens on the client side, you should either:

    1. Store the tokens in the session storage (not local storage) to limit the exposure to only the current tab or session
    2. Store the tokens in a service worker, this is much safer as workers run in a separate thread with isolated storage. but this is much more tricky in terms of state management because there is no guarantee that service workers will run persistently.

    That being said, The OAuth Working Group suggests storing tokens server-side, assuming the Single Page Application (SPA) has a corresponding backend. Here's how this might work:

    1. Users click "Login with Google" (Google is assumed for simplicity but it can be any other IDP) and are redirected to the backend endpoint /api/oauth/google. The backend initiates the OAuth or OIDC flow as a confidential client, retrieving the ID and access tokens.

    2. These tokens are validated, stored server-side (in-memory, relational database, or key-value stores like Redis), and linked to the client through a session ID returned to the frontend.

    3. Users are redirected back to the frontend with a session ID cookie

    This is the Backend For Frontend (BFF) pattern. acting as a proxy between the frontend and the backend, or being the backend itself. In this scenario, the frontend avoids handling authentication/authorization, sending requests to the backend which also auto-sends the session cookie. The backend handles the rest.

    Addressing your second question, while the frontend can technically obtain the authorization server's (AS) public key to validate tokens, it's usually not necessary. OAuth tokens are meant to be "opaque" to the client - the client only needs to attach the token as an Authorization header, without understanding its content. Token validation and authorization decisions are the resource server's duty. If your frontend requires user profile data, you can use the access token to call the AS's /userinfo endpoint or create a custom /userinfo or /me endpoint in the resource server.