Token Refresh
Learn how Wristband SDKs keep access tokens valid for the life of a session.
Access tokens issued by Wristband expire, but an application session usually lasts much longer. If your application uses the access token to call Wristband APIs or to protect its own backend APIs, the token must stay valid for as long as the user's session does. Backend server auth SDKs handle this by using the refresh token stored in the session to get new tokens from Wristband whenever the access token expires.
Refresh Function
Every backend server auth SDK provides the refreshTokenIfExpired() function that checks whether the access token has expired and, if it has, gets new tokens from Wristband. SDKs that include auth middleware also call this function automatically on each protected request.
If your application doesn't use the auth middleware for a particular code path, it can call refreshTokenIfExpired() directly. The function takes the refreshToken and the expiresAt values from the session, refreshes the tokens only if the access token has expired, and returns the new token data. Your application is then responsible for updating and saving the session.
For example, using the Express Auth SDK:
app.get('/api/orders', async (req, res) => {
const { expiresAt, isAuthenticated, refreshToken } = req.session;
if (!isAuthenticated) {
res.status(401).json({ error: 'Unauthorized' });
return;
}
if (refreshToken && expiresAt) {
try {
const tokenData = await wristbandAuth.refreshTokenIfExpired(refreshToken, expiresAt);
// A null result means the access token is still valid.
if (tokenData) {
req.session.accessToken = tokenData.accessToken;
req.session.expiresAt = tokenData.expiresAt;
req.session.refreshToken = tokenData.refreshToken;
await req.session.save();
}
} catch (error) {
res.status(401).json({ error: 'Unauthorized' });
return;
}
}
res.status(200).json({ orders: [] });
});When the access token has expired, the refreshTokenIfExpired() function calls the Wristband Token Endpoint on your Application's vanity domain using the Refresh Token grant type. The request authenticates with your clientId and clientSecret.
For example:
POST https://yourapp-yourcompany.us.wristband.dev/api/v1/oauth2/token
grant_type=refresh_token&refresh_token=98yht308hf902hc90wh09Arguments
The SDK can only refresh tokens when the session contains both of the following values, which the session's fromCallback() function stores from the callback data:
| Argument | Type | Required | Description |
|---|---|---|---|
expiresAt | number | Yes | The access token's expiration time, in milliseconds since the Unix epoch. |
refreshToken | string | Yes | The refresh token to send to Wristband when the access token has expired. Only present if the offline_access scope was requested during login and the Refresh Token grant type is enabled on your OAuth2 Client. The offline_access scope is included in the default scopes configuration. |
If either value is missing from the session, the auth middleware skips token refresh. Calling the refresh function directly without them throws an error.
Return Value
The refreshTokenIfExpired() function returns one of the following:
null: The access token hasn't expired yet, so no refresh was needed.- Token data: The access token had expired, and the SDK got new tokens from Wristband.
| Token Data Field | Type | Description |
|---|---|---|
accessToken | string | The new access token for calling Wristband APIs and protecting your application's backend APIs. |
expiresAt | number | The new access token's expiration time, in milliseconds since the Unix epoch, with the tokenExpirationBuffer already subtracted. |
expiresIn | number | The number of seconds until the new access token expires, with the tokenExpirationBuffer already subtracted. |
idToken | string | The new ID token, which identifies the user and contains claims about them. |
refreshToken | string or undefined | The refresh token returned by Wristband, to store in the session for the next refresh. |
Errors
The refreshTokenIfExpired() function throws in the following cases:
| Error | Code | When It Occurs |
|---|---|---|
TypeError | N/A | The refreshToken is empty, or expiresAt is missing or negative. |
WristbandError | invalid_refresh_token | Wristband rejected the refresh token with a client error (4xx), such as when the refresh token has expired or been revoked. |
WristbandError | unexpected_error | The refresh request failed for any other reason, such as a server error or network failure that persisted after all retries. |
When the refresh token is invalid, it can't be used again, and the user needs to start a new login.
When Tokens Are Refreshed
The SDK takes the tokenExpirationBuffer SDK configuration and subtracts that value from any new token's lifetime in order to calculate the new expiresAt and expiresIn values, similar to the callback() function.
The SDK treats an access token as expired once the current time reaches its expiresAt value. Since the tokenExpirationBuffer is already subtracted from expiresAt, the SDK refreshes tokens slightly before they actually expire, which helps avoid tokens expiring in the middle of an API call. The default buffer is 60 seconds.
Refresh happens on demand, not on a timer. The SDK only checks the token's expiration when a request passes through the auth middleware, or when your application calls the refresh function directly. If the access token is still valid, the SDK does nothing.
Retries
If the refresh request fails with a server error (5xx) or a network failure, the SDK retries it, up to three attempts in total, waiting 100 milliseconds before the first retry and doubling the wait after each one. Client errors (4xx) aren't retried, since they mean the refresh token itself is invalid, such as when it has expired or been revoked.
Using the Auth Middleware
SDKs that include auth middleware call the refresh function automatically on every request to a protected route, after validating the session and before the route handler runs, so your route handlers don't need to call it themselves. Your application creates the auth middleware from the SDK instance and applies it to the routes and APIs that require authentication.
For example, using the Express Auth SDK:
import { createWristbandAuth } from '@wristband/express-auth';
export const wristbandAuth = createWristbandAuth({
clientId: 'replace-me-with-your-client-id',
clientSecret: 'replace-me-with-your-client-secret',
wristbandApplicationVanityDomain: 'replace-me-with-your-vanity-domain',
});
export const requireWristbandAuth = wristbandAuth.createAuthMiddleware({
authStrategies: ['SESSION'],
sessionConfig: {
sessionOptions: {
secrets: process.env.SESSION_SECRET!,
},
},
});
// Expired access tokens are refreshed before the route handler runs.
app.get('/api/orders', requireWristbandAuth, (req, res) => {
res.status(200).json({ orders: [] });
});See Protecting Routes and APIs for more on configuring the auth middleware.
When a request reaches a route protected by the auth middleware, the middleware validates the session (and the CSRF token, if CSRF protection is enabled), and then refreshes the access token if it has expired. When a refresh occurs, the middleware updates the following values in the session:
accessTokenexpiresAtrefreshToken
The middleware then saves the session, so the updated tokens are written to the session cookie on the response. The route handler runs with the refreshed tokens already in the session.
JWT StrategyThe
JWTstrategy doesn't refresh tokens. It validates the Bearer token sent in the request'sAuthorizationheader and doesn't use the session. In SDKs whose auth middleware can try multiple strategies, token refresh only happens when theSESSIONstrategy authenticates the request.
Handling Refresh Failures
If the token refresh fails, such as when the refresh token has been revoked or Wristband can't be reached after all retries, the access token can't be renewed, and the request should be treated as unauthenticated:
- Auth middleware: For API routes, the middleware returns a
401 Unauthorizedresponse. For SDKs that protect server-rendered pages, the middleware redirects the user to your Login Endpoint instead (e.g. Next.js). - Manual refresh: The refresh function throws an error, and your application decides how to respond.
The SDK doesn't clear the session when a refresh fails, so it's up to your client to handle the 401 response and clean up any session data.
Updated about 4 hours ago