Handle Token Expiration
Implement automatic token refresh to prevent authentication failures in long-running applications.
What You'll Need
- Client ID and Client Secret - Your SASHA Partner credentials (see Prerequisites)
- Access to Partner API - Familiarity with getting an access token
Understanding Token Expiration
See Authentication Concepts to understand why tokens expire and the trade-offs between reactive vs. proactive refresh strategies.
Reactive Refresh
Detect 401
errors and refresh the token on demand:
- Node.js (REST)
- Node.js (gRPC)
const makeAuthenticatedRequest = async (url, options = {}) => {
const makeRequest = async (token) => {
const response = await fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${token}`,
},
});
if (response.status === 401) {
// Token expired, return null to trigger refresh
return null;
}
return response;
};
// Try with current token
let response = await makeRequest(currentToken);
// If unauthorized, refresh token and retry
if (response === null) {
console.log("Token expired, refreshing...");
currentToken = await refreshToken();
response = await makeRequest(currentToken);
}
return response;
};
const refreshToken = async () => {
const response = await fetch("https://partner.api.sasha.eu/oauth/token", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Authorization: `Basic ${btoa(`${CLIENT_ID}:${CLIENT_SECRET}`)}`,
},
body: "grant_type=client_credentials",
});
const data = await response.json();
return data.access_token;
};
// Usage
const response = await makeAuthenticatedRequest(
"https://partner.api.sasha.eu/jobs/job_123"
);
import { createChannel, createClient, Metadata } from "nice-grpc";
import { Status } from "nice-grpc-common";
import { PartnerAPIClient } from "./generated/partner_api_grpc_pb.js";
let currentToken = null;
const refreshToken = async () => {
const channel = createChannel("partner.api.sasha.eu:443");
const client = createClient(PartnerAPIClient, channel);
const response = await client.authenticateWithClientCredential({
clientId: CLIENT_ID,
clientSecret: CLIENT_SECRET,
});
currentToken = response.accessToken;
return currentToken;
};
const makeAuthenticatedCall = async (clientMethod, request) => {
const makeCall = async (token) => {
const metadata = new Metadata();
metadata.set("authorization", `Bearer ${token}`);
try {
return await clientMethod(request, { metadata });
} catch (error) {
if (error.code === Status.UNAUTHENTICATED) {
return null; // Token expired
}
throw error; // Other errors
}
};
// Try with current token
let response = await makeCall(currentToken);
// If unauthenticated, refresh token and retry
if (response === null) {
console.log("Token expired, refreshing...");
await refreshToken();
response = await makeCall(currentToken);
}
return response;
};
// Usage
const channel = createChannel("partner.api.sasha.eu:443");
const client = createClient(PartnerAPIClient, channel);
const response = await makeAuthenticatedCall(
client.getJob.bind(client),
{ jobId: "job_123" }
);
Proactive Refresh
Check token expiration before each request and refresh if needed:
- Node.js (REST)
- Node.js (gRPC)
let token = null;
let expiresAt = null;
const getToken = async () => {
// Refresh if no token or expires in less than 5 minutes
if (!token || Date.now() > expiresAt - 5 * 60 * 1000) {
const response = await fetch("https://partner.api.sasha.eu/oauth/token", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Authorization: `Basic ${btoa(`${CLIENT_ID}:${CLIENT_SECRET}`)}`,
},
body: "grant_type=client_credentials",
});
const data = await response.json();
token = data.access_token;
expiresAt = Date.now() + data.expires_in * 1000;
}
return token;
};
// Usage
const token = await getToken();
const response = await fetch("https://partner.api.sasha.eu/jobs/job_123", {
headers: { Authorization: `Bearer ${token}` }
});
import { createChannel, createClient, Metadata } from "nice-grpc";
import { PartnerAPIClient } from "./generated/partner_api_grpc_pb.js";
const channel = createChannel("partner.api.sasha.eu:443");
const client = createClient(PartnerAPIClient, channel);
let token = null;
let expiresAt = null;
const getToken = async () => {
// Refresh if no token or expires in less than 5 minutes
if (!token || Date.now() > expiresAt - 5 * 60 * 1000) {
const response = await client.authenticateWithClientCredential({
clientId: CLIENT_ID,
clientSecret: CLIENT_SECRET,
});
token = response.accessToken;
expiresAt = Date.now() + response.expiresIn * 1000;
}
return token;
};
// Usage
const token = await getToken();
const metadata = new Metadata();
metadata.set("authorization", `Bearer ${token}`);
const response = await client.getJob({ jobId: "job_123" }, { metadata });
Production Considerations
For production applications:
- Retry failed refreshes - Use a retry library with exponential backoff
- Prevent concurrent refreshes - Store a single refresh promise and await it across multiple requests
- Secure credentials - Use environment variables or secret management systems, never hardcode
See Also
- Authentication Concepts - Understanding token expiration strategies
- Quickstart - Getting your first token
- Rate Limiting - Handle rate limit errors