We’re currently developing a Next.js application (server side rendering) and are looking for secure ways to keep the users logged in for longer periods of time.

AFAIK this can either be done using silent authentication or refresh tokens. General note: When a user is not logged in yet, we can redirect the user to a login page. If the user enters their credentials, we use the Authorisation Code Grant (to my knowledge PKCE is not needed in this case as it’s all server side during these steps) that will redirect back and respond with an authorisation code. We can then exchange this authorisation code with an access token (and refresh token) using a client secret (all server side).

Refresh Tokens

Since any client side storage (local storage, cookies, etc.) is not safe (XSS attacks) for storing any kind of tokens (especially refresh tokens), we are wondering if it’s generally safe to store a refresh token (and access token) in a HTTP only cookie considering that…

  • … the token values are encrypted, e.g. AES, with a secret that is not exposed to the client side.
  • … the refresh tokens are rotating, so when you retrieve a new access token with your refresh token, you also receive a new refresh token. The old refresh token is invalidated and if used again, all refresh tokens are invalidated.
  • … the refresh token automatically expires after a couple of days, e.g. 7 days.

Silent Authentication

A possible alternative could be silent authentication via an auth request on the server side (prompt=none). The auth session for the silent authentication would also be stored in a HTTP only cookie.

In both scenarios, it’s probably necessary to make sure that the client doesn’t know about any of these tokens (You could potentially use silent authentication on the client side using an iframe (the domain is the same, just different subdomains) but the client would then potentially receive a new access tokens which has to be stored in memory (potential XSS vulnerability)).

Since it’s a server side rendered SPA, the client side still needs to be able to get new data from the API server using the access token. For this, we were thinking of using Next.js API routes as a proxy: So, if the client wants to get new data, it will send an AJAX request to the respective Next.js API route. The controller for this Next.js API route is able to read and decrypt the HTTP only cookie and can therefore send the request to the API server with a valid access token in the HTTP header. Just before the short lived access token expired, the controller would need to first send a request to the auth server to retrieve a new access (and refresh) token and then continue sending the request with the new access token to the API server.

While this sounds good and feasible in theory, we are wondering about the following points:

  1. Is it generally safe to save a (rotating) refresh and access token in a HTTP only cookie? Does the cookie value need to be encrypted or is that unnecessary? Does a rotating refresh token offer any additional security in this case?
  2. Is the “Next.js API route as a proxy” method a secure way to make sure that the client side can get new data from the API server? If e.g. otherdomain.com would try to send a request to the (“unprotected”) Next.js API route, it would not respond with any data as it’s a different domain and the HTTP only cookies therefore not accessible, correct? Is CSRF possible for these Next.js API routes?
  3. Is it safe if the HTTP only cookie for the refresh token is shared across all subdomains and not tied to one specific subdomain (application)? This would allow us to access the cookie from e.g. the actual website or other subdomains.
  4. Is the refresh token approach better / safer than the silent authentication approach?

Follow-Up question: Can the refresh token approach also be used the authenticate users in a browser extension? So:

  1. The user logs in (Authorisation Code Grant with PKCE): The login prompt/page is shown in a popup (or new tab) and the communication (authorisation code) is done through postMessage.
  2. The background script receives the authorisation code and exchanges it for an access token and rotating refresh token (which is probably necessary in this flow (?)) using the code and a code verifier. These tokens can then be saved in Chrome storage. We can potentially also encrypt the tokens but I’m not sure if that offers any additional protection (?) considering that the background script is not the same as a server.
  3. If the Chrome extension wants to receive data from the API server, it sends a message to the background script which will then send the API request using the tokens saved in Chrome storage.

Question 1

Put the auth token in an httpOnly cookie is good.
But the cookie needs to have the secure and SameSite flags too.

Encrypting the value of the cookie does not enhance the security. The client does not need to know the cleartext value, he only submits the encrypted one and the server allows the authentication based on this value. So the encrypted value is the secret.
If an attacker stole the encrypted cookie, he does not need to decrypt it, he just has to send it directly to the server.
Generate random token of 16 bytes (unpredictable) is sufficient, no need to encrypt.

Your refresh token seems to be unnecessarily. You can simply use the session token and make the server renew it each time it is submitted and accepted by the user.
This way, the session token is a "burn after reading" secret which cannot be used twice.
You can still implement the mechanism where all the session token are invalidated if an old one is submitted (so if an attacker stole a token, he will loose access after the next refresh of the user).
The session token can be automatically submitted each minutes to strongly limit the attack windows to use a stolen one.

The expiration is a key component for the security of the design. The idea is about "how many times my user let his session unlocked with a possible attempt of rogue physical access ?".
There is no absolute answer about the correct value. If your app manages banking data, the expiration should be one hour. If it only manage data about the loved hearthstone decks of the owner... 7 days is ok.
The best practices recommends using non persistent token (they expire when the browser/app is closed).

Question 2

This scheme seems to be unnecessarily complicated. The simple use of a session token as an API key should be sufficient is the server manage the permissions of the user associated with his session token.

Question 3

Using the same cookie over all the subdomains with a wildcard (*.mydomain.com) is commonly discouraged. But it is okay to explicitly mention each subdomain.

The following article adds some consideration to the cookies scope best practices : https://www.acunetix.com/blog/articles/why-scoping-cookies-to-parent-domains-is-a-bad-idea/

Question 4

Both approach seems to have the same degree of security.

  • Thanks, one additional question regarding Question 2: I understand that the refresh token method is more complicated (with similar security) than the session cookie. However, with both approaches, the cookie is saved in a HTTP only cookie. So, in order for the client side to receive data from the API, the client side needs to send a request to the server side, e.g. Next.js API route, which can then send the request to the API incl. the token. Since this Next.js API route is not secured, I assume I still need to use CORS and/or (?) CSRF tokens to protect these endpoints, correct? – Daniel Sep 20 '20 at 14:19
  • Also, the idea behind the encryption was to prevent e.g. browser extensions to potentially get the HTTP only cookie value and directly send it to the API. The latter is possible since the API has to allow cross origin requests (can be used by third parties as well). Since the Next.js API routes are only used by the application itself, we can use CORS and/or CSRF to make it more difficult for attackers to actual use these values. – Daniel Sep 20 '20 at 14:27
  • encryption will not address this problem. A browser extension will still be able to get the encrypted cookie and use it like the browser use it. I'm not familiar enough with next.js but if it acts like a gateway between the client and the API, the data acceded through the authentication token should not be accessible to someone who establish a direct contact with the API. The "zero trust" model must be enforced so the authentication needs to be checked on each element of the design – Sibwara Sep 22 '20 at 13:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.