Session Management
SSO Version: 5.24.0
Overview
The SSO service uses OAuth 2.0 tokens to manage user sessions. Understanding token types, lifetimes, and refresh mechanisms is essential for building secure, seamless applications.
Token Types
The SSO service issues three types of tokens during OAuth 2.0 authentication:
1. Access Token
- Purpose: Used to authenticate API requests
- Lifetime: 1 hour (3600 seconds)
- Format: JWT (JSON Web Token)
- Usage: Include in
Authorization: Bearer TOKENheader - Storage: HTTP-only cookie (backend) or secure storage (backend)
// Example access token payload (decoded)
{
"sub": "user-uuid-123",
"iss": "https://sso.doneisbetter.com",
"aud": "your-client-id",
"exp": 1710000000,
"iat": 1709996400,
"scope": "openid profile email"
}2. ID Token
- Purpose: Contains user identity and app permissions
- Lifetime: 1 hour (3600 seconds)
- Format: JWT (JSON Web Token)
- Usage: Extract user info, role, and permission status
- Storage: HTTP-only cookie (backend)
// Example ID token payload (decoded)
{
"sub": "user-uuid-123",
"email": "user@example.com",
"name": "John Doe",
"role": "admin", // 'admin' or 'user'
"permissionStatus": "approved", // 'approved', 'pending', or 'revoked'
"iss": "https://sso.doneisbetter.com",
"aud": "your-client-id",
"exp": 1710000000,
"iat": 1709996400
}3. Refresh Token
- Purpose: Used to obtain new access and ID tokens
- Lifetime: 30 days (2592000 seconds)
- Format: Opaque string (not JWT)
- Usage: Exchange for new tokens before access token expires
- Storage: HTTP-only cookie (backend)
- Security: Single-use (rotated on each refresh)
Session Lifecycle
Understanding the complete session lifecycle helps you implement reliable authentication:
Phase 1: Initial Authentication
- User clicks "Sign in with SSO"
- Your app redirects to SSO authorization page
- User authenticates with SSO
- SSO redirects back to your app with authorization code
- Your backend exchanges code for tokens (access, ID, refresh)
- Your backend stores tokens in HTTP-only cookies
- Your backend redirects user to app
Phase 2: Active Session
- User makes requests to your app
- Your backend validates ID token for each request
- User info and permissions are extracted from ID token
- Access token is used for SSO API calls (if needed)
Phase 3: Token Refresh (Automatic)
- Access token expires after 1 hour
- Your backend detects expiry (checks
expclaim) - Your backend uses refresh token to get new tokens
- Old refresh token is invalidated (single-use)
- New tokens are stored in cookies
Phase 4: Session End
- User clicks "Sign out"
- Your backend revokes tokens with SSO
- Your backend clears cookies
- User is redirected to logout page
Validating Tokens
Your backend should validate tokens on every request to ensure session integrity:
Backend Session Validation
// Node.js/Express example
import jwt from 'jsonwebtoken';
// WHY: Middleware to validate session and extract user info
export function validateSession(req, res, next) {
const idToken = req.cookies.id_token;
const accessToken = req.cookies.access_token;
// Check if tokens exist
if (!idToken || !accessToken) {
return res.status(401).json({ error: 'Not authenticated' });
}
try {
// Decode ID token (contains user info)
const decoded = jwt.decode(idToken);
// WHY: Check token expiration
if (decoded.exp * 1000 < Date.now()) {
// Token expired, attempt refresh
return refreshAndRetry(req, res, next);
}
// WHY: Check app permission status
if (decoded.permissionStatus !== 'approved') {
return res.status(403).json({
error: 'APP_ACCESS_DENIED',
permissionStatus: decoded.permissionStatus
});
}
// Attach user info to request
req.user = {
userId: decoded.sub,
email: decoded.email,
name: decoded.name,
role: decoded.role,
permissionStatus: decoded.permissionStatus
};
next();
} catch (error) {
console.error('Token validation error:', error);
return res.status(401).json({ error: 'Invalid token' });
}
}Token Refresh Implementation
Implement automatic token refresh to maintain seamless user sessions:
Proactive Refresh (Recommended)
// Refresh tokens 5 minutes before expiry
const REFRESH_BUFFER = 5 * 60 * 1000; // 5 minutes
async function ensureValidToken(req, res, next) {
const idToken = req.cookies.id_token;
if (!idToken) {
return res.status(401).json({ error: 'Not authenticated' });
}
const decoded = jwt.decode(idToken);
const expiresAt = decoded.exp * 1000;
const now = Date.now();
// WHY: Refresh proactively before expiry
if (expiresAt - now < REFRESH_BUFFER) {
await refreshTokens(req, res);
}
next();
}
async function refreshTokens(req, res) {
const refreshToken = req.cookies.refresh_token;
if (!refreshToken) {
throw new Error('No refresh token available');
}
try {
// WHY: Exchange refresh token for new access and ID tokens
const response = await fetch(
'https://sso.doneisbetter.com/api/oauth/token',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'refresh_token',
refresh_token: refreshToken,
client_id: process.env.SSO_CLIENT_ID,
client_secret: process.env.SSO_CLIENT_SECRET
})
}
);
if (!response.ok) {
throw new Error('Token refresh failed');
}
const tokens = await response.json();
const { access_token, id_token, refresh_token: newRefreshToken } = tokens;
// WHY: Update cookies with new tokens
res.cookie('access_token', access_token, {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 3600000 // 1 hour
});
res.cookie('id_token', id_token, {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 3600000
});
res.cookie('refresh_token', newRefreshToken, {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 2592000000 // 30 days
});
console.log('Tokens refreshed successfully');
} catch (error) {
console.error('Token refresh error:', error);
// Clear invalid tokens
res.clearCookie('access_token');
res.clearCookie('id_token');
res.clearCookie('refresh_token');
throw error;
}
}Session Termination
Properly terminate sessions to ensure security:
Logout Implementation
// Backend logout endpoint
export async function logout(req, res) {
const accessToken = req.cookies.access_token;
try {
// WHY: Revoke tokens with SSO server
if (accessToken) {
await fetch('https://sso.doneisbetter.com/api/oauth/revoke', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token: accessToken,
client_id: process.env.SSO_CLIENT_ID,
client_secret: process.env.SSO_CLIENT_SECRET
})
});
}
} catch (error) {
console.error('Token revocation failed:', error);
// Continue with logout even if revocation fails
}
// WHY: Clear all session cookies
res.clearCookie('access_token');
res.clearCookie('id_token');
res.clearCookie('refresh_token');
res.json({ success: true });
}Best Practices
- ✅ Store tokens in HTTP-only cookies (never in localStorage/sessionStorage)
- ✅ Implement proactive token refresh (5 minutes before expiry)
- ✅ Validate tokens on every request (check expiry and permission status)
- ✅ Use refresh tokens correctly (they're single-use and rotate on refresh)
- ✅ Revoke tokens on logout (prevent reuse even if intercepted)
- ✅ Handle token expiry gracefully (redirect to login or show message)
- ✅ Monitor token operations (log refresh failures for debugging)
- ⚠️ Never expose tokens in URLs (use cookies or headers only)
- ⚠️ Never decode tokens in frontend (keep user info extraction server-side)
Troubleshooting
Token expired
Symptom: 401 errors, user logged out unexpectedly
Solution: Implement proactive token refresh (see above)
Refresh token invalid
Symptom: Token refresh fails with 401 error
Causes:
- Refresh token already used (they're single-use)
- Refresh token expired (30 day lifetime)
- User's access was revoked
Solution: Redirect user to login page
Permission status changed
Symptom: User was approved but now gets 403 errors
Cause: SSO admin changed user's permission status
Solution: Check permissionStatus in ID token and redirect accordingly
Session not persisting
Symptom: User logged out on page refresh
Solutions:
- Verify cookies are set with correct domain and path
- Ensure
SameSiteandSecureflags are set correctly - Check CORS configuration (credentials must be included)
Summary
- ☑️ Understand three token types: access, ID, and refresh
- ☑️ Implement token validation middleware
- ☑️ Implement proactive token refresh (5 min before expiry)
- ☑️ Store tokens in HTTP-only cookies
- ☑️ Revoke tokens on logout
- ☑️ Check
permissionStatuson every request