How does refresh token actually work?
Hello, I am building a remix app and transitioning to Kinde for refresh tokens.
I set the access token to expire in 1 minute to test token expiry.
If token expires and we make a call to
getToken()
I would expect it to give back the header with refreshed access token since the token is expired.
But it doesn't – and even if I call refreshTokens()
explicitly, it still gives back the outdated token.
After a hard refresh, the user goes into logged out state (since access token is still expired).
I would need to visit /kinde-auth/login
(which is smart to detect previous login and not require another login attempt) which will then redirect back to the app with a new token.
The behavior I am seeing is refresh token just isn't being taken into account at all.5 Replies
Hi J,
Thanks for reaching out.
It sounds like you're testing token expiry and refresh behavior using Kinde's Remix SDK, and encountering issues where the access token is not being refreshed as expected after expiry.
To clarify how the refresh flow works:
- Calling
getToken()
will return the current access token. If the token is expired, it will attempt a silent refresh behind the scenes.
- Calling refreshTokens()
does not directly return a new token. Instead, it updates the session via response headers. You’ll need to forward these headers in your Remix loader or action to ensure the browser receives the updated session cookies.
- To retrieve the new token after refreshing, call getToken()
again after refreshTokens()
completes successfully.
Also, note that if the access token hasn’t technically expired yet, the refresh call may return the current token instead of issuing a new one.
To ensure everything works correctly:
- Wait until the token is truly expired (based on the exp
field in the JWT).
- Forward the updated headers
from getKindeSession(request)
in your Remix Response
.
You can refer to our documentation for more detail here: You can refer to our documentation for more detail here: https://docs.kinde.com/developer-tools/sdks/backend/remix-sdk
Let us know if you'd like a working example integrated into your Remix app—we’d be happy to help further.i know for sure the token is truly expired when i was trying to refresh the token, but comparing the header's value vs current one, the values were the same
and i am calling this at the root loader and returning it via the response headers
Hi J,
Thanks for the update.
Just to simplify how Kinde handles refresh tokens in Remix:
-
getToken()
will return the current access token.
- To refresh it, you should call refreshTokens()
. But this doesn't return the new token directly.
- Instead, it updates the session using response headers.
- So in your Remix loader, make sure you include those headers in your response — that’s what updates the session in the browser.
- After that, you can call getToken()
again and it should return the refreshed token.
Let me know if you’d like a working example, I’d be happy to help set it upyes working example would be awesome. i dont understand the last "getToken()" call – should that happen client side? or server side? How would we access the access token if you are in a serversided rendering call, and the token is expired, but we need to fetch some protected API endpoints with a refreshed token?
Hi J,
Thanks for reach out. In SSR (server-side rendering) flows like Remix loaders/actions,
https://docs.kinde.com/developer-tools/sdks/backend/remix-sdk
Let me know if this helps or if you have any further questions
getKindeSession(request)
is called server-side.
- You call refreshTokens()
server-side first — it refreshes the session if needed.
- Then call getToken()
(also server-side) to get a fresh access token.
- Finally, use the refreshed token to call your protected APIs.
Example flow inside a Remix loader:
Summary:Docs for reference:
BothrefreshTokens()
andgetToken()
happen server-side in SSR routes.
You forward updated session headers too, so client keeps the new token in cookies.
https://docs.kinde.com/developer-tools/sdks/backend/remix-sdk
Let me know if this helps or if you have any further questions