Refresh Token Clarity
-
We are trying to utilize refresh tokens in a SPA web site. To do so we had to reduce our security settings by not requiring client authentication so that it was able to exchange the refresh token for an access token.
We are okay with this since we also have "require PKCE" if no client secret is passed in, but wanted to verify that the refresh token itself was not a source of risk.
I can't prove that the refresh token (essentially a bearer token now) is either:
- Sender Constrained
- Refresh Token Rotation Implemented
We did a test for #2 with "one time use" on, expecting the refresh chain to be rejected in the detection of an "attack".
Issue a /authorize and get a code
Exchange code for tokens (includes refresh token)
Use refresh token to get a new (access token, refresh token) pair
Use OLD refresh token again to attempt to get a new pair, this fails [so far so good]
Use NEW refresh token from step above to get a pairThe expectation is that the NEW refresh token is revoked due to the "attack" detected with the use of the OLD refresh token. Essentially we should assume that the credentials were stolen and used maliciously.
However, that doesn't happen with FA. Instead, the refresh token is allowed to get a new access token.
This allows the following attack:
- Attacker/Malicious code gains access to refresh token
- Attacker immediately uses refresh token to gain new access token
- Real code attempts to get a new access token and is refused due to one-time
- Attacker continues to use refresh chain to access application
I'm trying to prove that refresh tokens are "safe" to use in a public client and would like some guidance since I can't find any information about how the above attack is mitigated in Fusion Auth. Does anything else mitigate this attack?
Thanks!
-
@alan-wood That is the current behavior.
If you'd like to keep an eye on whether old refresh tokens are used, you might look at this webhook, which fires every time a JWT is refreshed and this one, which is fired every time a refresh token is revoked. Both of these have the userId and refreshToken.
You could watch for collisions (storing them in your database or redis or wherever) and manually revoke any refresh token in such a case.
Please file an issue in our github issues repo outlining your concern if you think this should be handled by FusionAuth. Feel free to reference this forum post.
-
@dan I've raised a new feature request to add this functionality to Fusion Auth. The webhook idea was interesting, but there is no call when the one-time JWT refresh token is "re-used". Therefore, I can't detect the scenario and revoke the chain. The alternative is to open the JWT refresh token to be re-usable. That's another set of security issues that I don't want to have in the system.
I've raised the issue here: Issue #1619
-
@alan-wood Hmmm.
First, thanks for filing the issue. I appreciate it.
but there is no call when the one-time JWT refresh token is "re-used".
Second, I'm pretty sure the webhook idea will work. Here's my thoughts:
- User 123 logs in, gets refresh token A
- Use refresh token to get a new (access token, refresh token) pair
- System catches jwt refresh event and records token A for this user (so the userId 123, token A pair). It generates token B.
- Use refresh token A again to attempt to get a new pair, this fails [so far so good]
- The webhook should fire again and records that token A was used again (by looking up the refresh token value in the pair). Uh-oh!
- Fire off an event to revoke all refresh tokens for the user 123: https://fusionauth.io/docs/v1/tech/apis/jwt#revoke-refresh-tokens
- Using refresh token B will fail, because all refresh tokens are revoked.
Have you tried this approach? What am I missing?