1

I am working on an authentication server that can act as a central place to manage authentication for multiple projects, sort of like keycloak or ory kratos.

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:

Secure Cookies

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.

Refresh Token Rotation

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

  1. Generate a Private/Public Key Pair
  2. Alongside the username and password send the generated public key to the authentication server
  3. 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
  4. The client stores the session ID and the private key in indexddb (set extractable to false)
  5. 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.

MDN: https://developer.mozilla.org/en-US/docs/Web/API/CryptoKey

Conclusion

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?

2
  • You say that in case of bad connection "the client loses the connection and never receives the new RT". But the same is applicable in your case on the step 5. How is your approach different? Why do you think you approach is resistant to bad connections? – mentallurg Feb 27 at 23:03
  • My approach would create a "single use" access token on the client and then exchange this AT with new AT from the authentication server that I can then use for the resource server. If the client loses the connection during the exchange and then go's back online you would simple generate a new AT – fragile_frogs Feb 27 at 23:19
0

Well, first of all, the simple approach to fixing the problem you face is to not invalidate the RT immediately upon use. Either just don't re-issue it at all (keep using it until the session ends due to user action, or the token expires), or wait to invalidate the old one until you've confirmed that the user received the new one (e.g. by checking the next request the user sends to the site and seeing if the new token is present. The second approach adds an additional DB access on each refresh (on the subsequent request), but you can safely store the "new token verified" flag on the client (possibly as part of the cookie, which gets updated during the verification) since the whole "make sure the user gets their new token" thing is just a convenience factor for the user anyhow and doesn't impact security.

As for your proposed implementation, it looks like it should work. However, the whole thing is complicated in a way that adds little actual security. You're using asymmetric signatures so that you can hide the private key from JS, which is only relevant in the case of XSS that happens at some point post-login (as most of them do). However, this - like the HttpOnly flag on cookies - is barely relevant security-wise. As long as the victim has any document (including in a window or iframe) open to the vulnerable page, the attacker can remote-control the victim's session and see all data. Assuming the access token is visible to script - which it must be, for a script to sign it for the refresh process - the attacker can use that token themselves in the background, making their own requests and viewing the responses. The attacker won't be able to refresh the AT once the victim closes all vulnerable pages, but realistically that doesn't matter. Either the attacker will have already achieved their goals, or they'll just use the XSS attack again when they need it.

5
  • "If an authorization server does choose to issue refresh tokens to browser-based applications, then it MUST issue a new refresh token with every access token refresh response. Doing this mitigates the risk of a leaked refresh token, as a leaked refresh token can be detected if both the attacker and the legitimate client attempt to use the same refresh token." - ietf – fragile_frogs Feb 28 at 0:06
  • "he whole "make sure the user gets their new token" thing is just a convenience factor for the user anyhow and doesn't impact security." That's the whole point of my question, my last sentence states that this won't add any extra security but rather improve the user experience in case something goes wrong. – fragile_frogs Feb 28 at 0:08
  • 1
    That whole section is full of unsupported and frankly silly claims. "browser-based applications provide many attack vectors by which a refresh token can be leaked" oh please. Web apps have lots of potential vulnerabilities but token leakage is one of the most trivially solved ones. As for mitigating refresh token leakage, that's both too late to prevent most harm and achievable in other ways. In fact, even their "MUST" only says you MUST issue a new refresh token (which allows detecting replay), not that you need to invalidate the old one (though maybe it makes that requirement elsewhere). – CBHacking Feb 28 at 0:41
  • Also, that draft is an old version, "expired" a year ago, and has been updated several times since it was written. Lots of changes to section 8 between what you linked and the current draft, tools.ietf.org/html/… *Edit to previous comment: s/trivially solved/lowest threat/. As your question highlights, it's not always trivial to completely avoid the risk... but the risk you're trying to mitigate is still pretty minor. – CBHacking Feb 28 at 0:45
  • Ahh thanks, I didn't see that the draft had expired. – fragile_frogs Feb 28 at 11:08

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.