K
Kinde•3mo ago
Mcgeckle

Getting and refreshing access tokens in API client ts file

Hi, I am working on protecting my API by passing the access token in the headers of the API calls and have a few questions to make sure I'm doing this properly using the React SDK. I am using openapi-fetch package to call my API and their docs give an example on adding middleware to pass the access token in each request (here). I am using the getRawToken, isAuthenticated, refreshToken utility functions provided in the @kinde-oss/kinde-auth-react/utils import to do this in my API client ts file similar to what is shown in the openapi-fetch docs I linked. 1. In the Kinde docs it states that if you use the getToken function it will automatically refresh the token if it is about to expire. But, when looking at the code I do not see how it would be doing this. Maybe I'm looking in the wrong place or not understanding something correctly (I'm no expert 😅 ). I have not been auto logged out or anything yet, so I assume it is refreshing as stated in the docs, but I just wanted to confirm. 2. Does the getRawToken utility function also auto refresh the token or does it need to be manually refreshed in these cases? Thanks!
7 Replies
Mcgeckle
McgeckleOP•3mo ago
Here is a copy of my current implementation of it for context:
import createClient, { Middleware } from "openapi-fetch";
import { paths } from "./types/schema";
import {
getRawToken,
isAuthenticated,
refreshToken,
} from "@kinde-oss/kinde-auth-react/utils";

let accessToken: string | undefined = undefined;

const authMiddleware: Middleware = {
async onRequest({ request }) {
if (!accessToken) {
accessToken = (await getRawToken()) || undefined;
}

const tokenIsValid = await isAuthenticated();

if (!tokenIsValid) {
const refreshTokenResponse = await refreshToken({
domain: import.meta.env.VITE_KINDE_DOMAIN,
clientId: import.meta.env.VITE_KINDE_CLIENT_ID,
});
accessToken = refreshTokenResponse.accessToken;
}

request.headers.set("Authorization", `Bearer ${accessToken}`);
return request;
},
};

const client = createClient<paths>({
baseUrl: import.meta.env.VITE_API_BASE_URL,
});
client.use(authMiddleware);

export default client;
import createClient, { Middleware } from "openapi-fetch";
import { paths } from "./types/schema";
import {
getRawToken,
isAuthenticated,
refreshToken,
} from "@kinde-oss/kinde-auth-react/utils";

let accessToken: string | undefined = undefined;

const authMiddleware: Middleware = {
async onRequest({ request }) {
if (!accessToken) {
accessToken = (await getRawToken()) || undefined;
}

const tokenIsValid = await isAuthenticated();

if (!tokenIsValid) {
const refreshTokenResponse = await refreshToken({
domain: import.meta.env.VITE_KINDE_DOMAIN,
clientId: import.meta.env.VITE_KINDE_CLIENT_ID,
});
accessToken = refreshTokenResponse.accessToken;
}

request.headers.set("Authorization", `Bearer ${accessToken}`);
return request;
},
};

const client = createClient<paths>({
baseUrl: import.meta.env.VITE_API_BASE_URL,
});
client.use(authMiddleware);

export default client;
But, if it does get auto refreshed then I'm assuming I can simplify this code to something like this isntead:
import createClient, { Middleware } from "openapi-fetch";
import { paths } from "./types/schema";
import {
getRawToken,
} from "@kinde-oss/kinde-auth-react/utils";

const authMiddleware: Middleware = {
async onRequest({ request }) {
const accessToken = await getRawToken()
request.headers.set("Authorization", `Bearer ${accessToken}`);
return request;
},
};

const client = createClient<paths>({
baseUrl: import.meta.env.VITE_API_BASE_URL,
});
client.use(authMiddleware);

export default client;
import createClient, { Middleware } from "openapi-fetch";
import { paths } from "./types/schema";
import {
getRawToken,
} from "@kinde-oss/kinde-auth-react/utils";

const authMiddleware: Middleware = {
async onRequest({ request }) {
const accessToken = await getRawToken()
request.headers.set("Authorization", `Bearer ${accessToken}`);
return request;
},
};

const client = createClient<paths>({
baseUrl: import.meta.env.VITE_API_BASE_URL,
});
client.use(authMiddleware);

export default client;
Roshan
Roshan•3mo ago
Hi Mcgeckle,
Thanks for reaching out!
We’ve flagged this with our team, and they’ll be looking into it shortly. We’ll keep you posted with any updates.
Mcgeckle
McgeckleOP•2mo ago
Great, thank you!
Roshan
Roshan•2mo ago
Hi Mcgeckle, Thanks for your detailed question and patience.
When using the React SDK, the recommended way to retrieve access tokens is via the getAccessToken() method from the useKindeAuth() hook. This method: - Automatically returns a cached access token if it’s still valid. - Automatically refreshes access token if it’s about to expire.
Testing Note Just to validate everything for you, we temporarily set the access token expiry time to 2 minutes in our test environment and confirmed that: - The getAccessToken() method automatically refreshed the token once it expired. - A new token (with a new jti) was issued when we tried to do action using access token after 2 mins By default, access tokens are valid for 86400 seconds (24 hours).


Here’s a simplified and recommended version using getAccessToken() Ref:https://docs.kinde.com/developer-tools/sdks/frontend/react-sdk/#call-your-api
import createClient, { Middleware } from "openapi-fetch";
import { paths } from "./types/schema";

import createClient, { Middleware } from "openapi-fetch";
import { paths } from "./types/schema";

import { useKindeAuth } from "@kinde-oss/kinde-auth-react";

import { useKindeAuth } from "@kinde-oss/kinde-auth-react";

const authMiddleware: Middleware = {

const authMiddleware: Middleware = {

async onRequest({ request }) {

async onRequest({ request }) {

const { getAccessToken } = useKindeAuth();

const { getAccessToken } = useKindeAuth();

const accessToken = await getAccessToken();

const accessToken = await getAccessToken();

if (accessToken) {

if (accessToken) {

request.headers.set("Authorization", Bearer ${accessToken});

request.headers.set("Authorization", Bearer ${accessToken});

}

}

return request;

return request;

},

},

};

};


Please let us know if this helps.
Mcgeckle
McgeckleOP•2mo ago
Hi, thank you for the response. The problem is I'm trying to use it in the ts file where the api client is created and I can't use the useKindeAuth hook in a ts file. I'm trying to avoid having to call getAccessToken inside of all the hooks or components where I need to make an api call. That is why I'm trying to use the getRawToken utility function instead, since I can call it in the ts file. Does the getRawToken utility function not auto refresh like getAccessToken does and if not is manually refreshing as I am in my first code snippet a valid solution to this? Also I noticed in my testing that it seems like the token gets auto-refresh in the background even without calling any methods. If I let the page sit and set a short expiration time I will see a call to the token endpoint on the dev tools network tab. If this is true then it seems like this may only be an issue in a rare coincidence where the function is called around the same time the refresh is called in the background. But I should still be sure that it is properly refreshing on calls either way just incase.
Roshan
Roshan•2mo ago
Hi,
Thanks for the detailed follow-up. getRawToken vs getAccessToken - The getRawToken utility function does not auto-refresh the access token. It simply retrieves the current token from memory or browser storage (e.g. sessionStorage) - On the other hand, getAccessToken (from useKindeAuth) is designed to return a valid token, and benefits from the SDK's built-in silent refresh logic, which keeps tokens fresh in the background without needing manual intervention.

Silent Token Refresh in the Background Yes, what you're observing in the DevTools Network tab is correct, the SDK does schedule silent refreshes automatically: - It uses a timer (via setRefreshTimer) to refresh the token shortly before expiration. - It also refreshes the token when the tab becomes active (via visibilitychange events). - These are implemented in the KindeProvider component and are triggered as long as the app remains mounted and the user is authenticated. So even if you're not calling any SDK method, you may still see oauth2/token calls, that’s expected and by design.
Mcgeckle
McgeckleOP•2mo ago
Okay, thank you!

Did you find this page helpful?