While working on implementing refresh_tokens (RT) I got an idea for an alternative solution that would not rely on cookies. But first, what challenges do I see with using refresh_tokens:
I am currently storing the RT inside an HttpOnly cookie with the SameSite configuration set to strict to avoid the browser leaking the RT. This also requires me to either 1) have the authorization server run on the same site (e.g. auth.example.com) or 2) proxy the request though my API server to the authentication server.
When a client asks for a new access token (AT) the old RT get invalidated and the client receives a new pair of AT and RT, this works well for the happy case where things don't break but IMO creates a bad UX in case something goes wrong e.g. the user is on a bad internet connection and request a new AT, while the server is processing the request the client loses the connection and never receives the new RT, now the user is in an invalid state and needs to sign in again.
A cookieless solution
- Generate a Private/Public Key Pair
- Alongside the username and password send the generated public key to the authentication server
- On success the authentication server returns a session ID that can be used to associate the current session with the public key that was send to the server
- The client stores the session ID and the private key in indexddb (set extractable to false)
- In order to request a new access token for a given session the client calls the authentication server with a short lived Access Token signed with the private key.
Private Key Storage
In order to prevent private keys from leaking outside the browser context we need to configure the CryptoKey object to not be extractable, this allows us to use the private key to sign the AT without directly accessing the key and preventing XSS.
I think this approach would have the same security characteristics compared to using refresh tokens but can provide a better UX in case of failure, additional I don't have to run the authentication server on the same site as my client projects.
Am I missing something?