K
Kinde7d ago
j

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
Ages - Kinde
Ages - Kinde7d ago
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.
j
jOP6d ago
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
export async function loader({ request }: LoaderFunctionArgs) {
let user: User | null = null;

// calls kinde's getToken() under the hood
const authToken = await getAuthToken(request);
if (authToken) {
const meResponse = await getMe(request);
if (!meResponse.successData) {
// Throw an error to be caught by the ErrorBoundary
throw new Error("Failed to fetch user data.");
}
user = meResponse.successData;
}


return data(
{
user,
publicEnv: getPublicEnvFromServer(),
},
{
// returns { headers } = getKindeSession(request)
headers: await getAuthHeaders(request),
}
);
}
export async function loader({ request }: LoaderFunctionArgs) {
let user: User | null = null;

// calls kinde's getToken() under the hood
const authToken = await getAuthToken(request);
if (authToken) {
const meResponse = await getMe(request);
if (!meResponse.successData) {
// Throw an error to be caught by the ErrorBoundary
throw new Error("Failed to fetch user data.");
}
user = meResponse.successData;
}


return data(
{
user,
publicEnv: getPublicEnvFromServer(),
},
{
// returns { headers } = getKindeSession(request)
headers: await getAuthHeaders(request),
}
);
}
Ages - Kinde
Ages - Kinde6d ago
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 up
j
jOP5d ago
yes 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?
Ages - Kinde
Ages - Kinde2d ago
Hi J, Thanks for reach out. In SSR (server-side rendering) flows like Remix loaders/actions, 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:
export const loader = async ({ request }) => {
const { refreshTokens, getToken, getUser, getSessionHeaders } = await getKindeSession(request);

await refreshTokens(); // Refresh session (if needed)

const accessToken = await getToken(); // Get updated access token

const user = await getUser();

// Call protected APIs with the new token
const res = await fetch('https://your-api.com/protected', {
headers: { Authorization: `Bearer ${accessToken}` }
});

return json(await res.json(), { headers: getSessionHeaders() });
};
export const loader = async ({ request }) => {
const { refreshTokens, getToken, getUser, getSessionHeaders } = await getKindeSession(request);

await refreshTokens(); // Refresh session (if needed)

const accessToken = await getToken(); // Get updated access token

const user = await getUser();

// Call protected APIs with the new token
const res = await fetch('https://your-api.com/protected', {
headers: { Authorization: `Bearer ${accessToken}` }
});

return json(await res.json(), { headers: getSessionHeaders() });
};
Summary:
Both refreshTokens() and getToken() happen server-side in SSR routes.
You forward updated session headers too, so client keeps the new token in cookies.
Docs for reference:
https://docs.kinde.com/developer-tools/sdks/backend/remix-sdk

Let me know if this helps or if you have any further questions

Did you find this page helpful?