For the complete documentation index, see llms.txt. This page is also available as Markdown.
Tokens Renewal
Many OpenID Connect adapters, end up implementing token renewal with a background refresh loop.
That approach often creates avoidable load and some tricky edge cases.
With oidc-spa, token lifecycle management is handled for you and stays out of your app code.
The Problem With Access Token Refresh Loops
Access tokens are meant to be short-lived (typically ~5 minutes, but sometimes as little as 20 seconds for high-security apps).
Many adapters try to keep an access token “always fresh” in cache, which leads to:
Constant background refreshes
Heavy load on your auth server
Agravated load when mutiple tabs are open on your app.
This isn’t needed. You don’t need a valid access token cached at all times.
The Better Approach (What oidc-spa Does)
Whenever you need to make an authenticated request, just ask oidc-spa for a token:
constoidc=awaitgetOidc();if (!oidc.isUserLoggedIn) {throwError("Logical error in our application flow");}const{accessToken}=awaitoidc.getTokens();headers.set("Authorization",`Bearer ${accessToken}`);
If a valid token is cached, you’ll get it.
If it’s expired or soon to expire, oidc-spa silently refreshes it using the refresh token.
Behind the scenes, oidc-spa ensures the session never expires prematurely by refreshing at least once before the refresh token itself expires.
This prevents the backend from destroying the session simply because the user wasn’t making authenticated requests (e.g., they’re filling out a form or browsing content).
At the same time, oidc-spa tracks actual user activity (keyboard, mouse, touch). If the user is truly idle beyond the refresh token lifespan, they’re logged out as expected.
Why oidc-spa Still Exposes renewTokens()
There are two legitimate edge cases:
After custom requests: If you make a request to your OIDC server that changes claims in the id_token or access_token, call renewTokens() to ensure you have the latest values. (This is a rare use case. It usually happens when user info is updated outside your app. If you’re not sure, you can generally assume you don’t need this.)
Getting a freshly issued token: If at one point in time, you want to be sure that you have a freshly issued token with it's maximum lifetime you might want to call renewTokens() before you call getTokens()
Custom token parameters: If your OIDC server supports extra token endpoint params, you can trigger a refresh with them. (extraTokenParams is also available at createOidc() time.)
Outside of these rare cases, you never need to call renewTokens() manually.
import { createOidc } from "oidc-spa/core";
const prOidc = await createOidc({ ... });
// Function to call when we want to renew the token
export function renewTokens(){
const oidc = await prOidc;
if( !oidc.isUserLoggedIn ){
throw new Error("Logical error");
}
oidc.renewTokens(
// Optionally you can pass extra params that will be added
// to the body of the POST request to the openid-connect/token endpoint.
// { extraTokenParams: { electedCustomer: "customer123" } }
// This parameter can also be provided as parameter to the createOidc
// function. See: https://github.com/keycloakify/oidc-spa/blob/59b8db7db0b47c84e8f383a86677e88e884887cb/src/oidc.ts#L153-L163
);
}
// Subscribing to token renewal
prOidc.then(oidc => {
if( !oidc.isUserLoggedIn ){
return;
}
const {
unsubscribeFromTokensChange
} = oidc.subscribeToTokensChange(tokens => {
console.log("Token Renewed", tokens);
});
setTimeout(() => {
// Call unsubscribe when you want to stop watching tokens change
unsubscribeFromTokensChange();
}, 10_000);
});
import { getOidc } from "~/oidc";
// Function to call when we want to renew the token
export function renewTokens(){
const oidc = await getOidc({ assert: "user logged in" });
oidc.renewTokens(
// Optionally you can pass extra params that will be added
// to the body of the POST request to the openid-connect/token endpoint.
// { extraTokenParams: { electedCustomer: "customer123" } }
// This parameter can also be provided as parameter to the createOidc
// function. See: https://github.com/keycloakify/oidc-spa/blob/59b8db7db0b47c84e8f383a86677e88e884887cb/src/oidc.ts#L153-L163
);
}
// Subscribing to token renewal
getOidc().then(oidc => {
if( !oidc.isUserLoggedIn ){
return;
}
const {
unsubscribeFromAccessTokenRotation
} = oidc.subscribeToAccessTokenRotation(accessToken => {
console.log("Access Token Rotated!", accessToken);
});
const {
unsubscribeFromDecodedIdTokenChange
} = oidc.subscribeToDecodedIdTokenChange(decodedIdToken => {
console.log(`Decoded id token change`, decodedIdToken);
});
setTimeout(() => {
// Call unsubscribe when you want to stop watching tokens change
unsubscribeFromAccessTokenRotation();
unsubscribeFromDecodedIdTokenChange();
}, 10_000);
});