Start after completing a login flow from the authentication flows overview.
Token Refresh Workflow
Access tokens expire after a period of time (typically 1 hour). Use refresh tokens to obtain new access tokens without requiring the user to log in again.
When to Refresh Tokens
- When an API call returns a 401 Unauthorized error
- Proactively before the access token expires (if you track expiration)
- On application startup if the stored token might be expired
Basic Token Refresh
async function refreshAccessToken() {
const refreshToken = localStorage.getItem('sso_refresh_token');
if (!refreshToken) {
// No refresh token - user needs to log in
window.location.href = '/login';
return null;
}
try {
const tokens = await sso.auth.refreshToken(refreshToken);
// Store new tokens (both are rotated)
localStorage.setItem('sso_access_token', tokens.access_token);
localStorage.setItem('sso_refresh_token', tokens.refresh_token);
// Update SDK
sso.setAuthToken(tokens.access_token);
return tokens.access_token;
} catch (error) {
console.error('Token refresh failed:', error);
// Refresh token invalid or expired - user must log in
localStorage.removeItem('sso_access_token');
localStorage.removeItem('sso_refresh_token');
window.location.href = '/login';
return null;
}
}
Automatic Token Refresh with Interceptor
Implement automatic token refresh by intercepting 401 errors:
import { SsoClient, SsoApiError } from '@drmhse/sso-sdk';
class AuthenticatedSsoClient {
private sso: SsoClient;
private isRefreshing: boolean = false;
private refreshPromise: Promise<string | null> | null = null;
constructor(baseURL: string) {
const accessToken = localStorage.getItem('sso_access_token');
this.sso = new SsoClient({ baseURL, token: accessToken || undefined });
}
async request<T>(fn: () => Promise<T>): Promise<T> {
try {
return await fn();
} catch (error) {
if (error instanceof SsoApiError && error.statusCode === 401) {
// Token expired, try to refresh
const newToken = await this.refreshToken();
if (newToken) {
// Retry the original request with new token
return await fn();
}
}
throw error;
}
}
private async refreshToken(): Promise<string | null> {
// Prevent multiple simultaneous refresh attempts
if (this.isRefreshing && this.refreshPromise) {
return this.refreshPromise;
}
this.isRefreshing = true;
this.refreshPromise = this.performRefresh();
const result = await this.refreshPromise;
this.isRefreshing = false;
this.refreshPromise = null;
return result;
}
private async performRefresh(): Promise<string | null> {
const refreshToken = localStorage.getItem('sso_refresh_token');
if (!refreshToken) {
this.redirectToLogin();
return null;
}
try {
const tokens = await this.sso.auth.refreshToken(refreshToken);
localStorage.setItem('sso_access_token', tokens.access_token);
localStorage.setItem('sso_refresh_token', tokens.refresh_token);
this.sso.setAuthToken(tokens.access_token);
return tokens.access_token;
} catch (error) {
console.error('Token refresh failed:', error);
this.redirectToLogin();
return null;
}
}
private redirectToLogin() {
localStorage.removeItem('sso_access_token');
localStorage.removeItem('sso_refresh_token');
window.location.href = '/login';
}
getClient(): SsoClient {
return this.sso;
}
}
// Usage
const authClient = new AuthenticatedSsoClient('https://sso.example.com');
// Wrap all API calls with automatic retry
const profile = await authClient.request(() =>
authClient.getClient().user.getProfile()
);
React Hook for Token Management
import { useEffect, useState } from 'react';
import { SsoClient } from '@drmhse/sso-sdk';
export function useAuth() {
const [sso, setSso] = useState<SsoClient | null>(null);
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
initializeAuth();
}, []);
const initializeAuth = async () => {
const accessToken = localStorage.getItem('sso_access_token');
const refreshToken = localStorage.getItem('sso_refresh_token');
if (!accessToken || !refreshToken) {
setIsLoading(false);
return;
}
const client = new SsoClient({
baseURL: process.env.REACT_APP_SSO_URL!,
token: accessToken
});
try {
// Try to fetch profile to verify token
await client.user.getProfile();
setSso(client);
setIsAuthenticated(true);
} catch (error) {
// Token invalid, try to refresh
try {
const tokens = await client.auth.refreshToken(refreshToken);
localStorage.setItem('sso_access_token', tokens.access_token);
localStorage.setItem('sso_refresh_token', tokens.refresh_token);
client.setAuthToken(tokens.access_token);
setSso(client);
setIsAuthenticated(true);
} catch (refreshError) {
// Refresh failed, clear tokens
localStorage.removeItem('sso_access_token');
localStorage.removeItem('sso_refresh_token');
}
} finally {
setIsLoading(false);
}
};
const logout = async () => {
if (sso) {
try {
await sso.auth.logout();
} catch (error) {
console.error('Logout error:', error);
}
}
localStorage.removeItem('sso_access_token');
localStorage.removeItem('sso_refresh_token');
setSso(null);
setIsAuthenticated(false);
};
return {
sso,
isAuthenticated,
isLoading,
logout
};
}
// Usage in components
function App() {
const { sso, isAuthenticated, isLoading } = useAuth();
if (isLoading) {
return <div>Loading...</div>;
}
if (!isAuthenticated) {
return <LoginPage />;
}
return <Dashboard sso={sso!} />;
}
Best Practices
Token Storage
Web Applications:
- Use
localStoragefor single-tab persistence - Use
sessionStoragefor single-session security - Consider using secure HTTP-only cookies for sensitive applications
Mobile Applications:
- Use secure storage APIs (iOS Keychain, Android Keystore)
- Never store tokens in plain text files
CLI Applications:
- Store tokens in user home directory with restricted permissions
- Use OS-specific secure storage when available
Security Considerations
- HTTPS Only: Always use HTTPS in production
- Token Rotation: The platform implements automatic refresh token rotation
- XSS Protection: Sanitize all user inputs to prevent token theft
- CSRF Protection: Use state parameters for OAuth flows
- Logout Everywhere: Call
sso.auth.logout()to revoke tokens server-side