> For the complete documentation index, see [llms.txt](https://oten.gitbook.io/identity-support/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://oten.gitbook.io/identity-support/integration/prerequisites/pkce-implementation-guide/step-5-token-management.md).

# Step 5: Token Management

Proper token management is crucial for maintaining secure and seamless user sessions. This step covers secure token storage, automatic refresh, and best practices for token lifecycle management.

**Need context?** Check the [Integration Flow Overview](broken://pages/iMMrTD1lQOMbxxvheKAz) to see how this step fits into the complete process.

## What You'll Learn

In this step, you will:

* Store tokens securely in different environments
* Implement automatic token refresh
* Handle token expiration gracefully
* Manage token lifecycle and cleanup
* Use tokens for API calls
* Implement proper security measures

## Secure Token Storage

### Server-Side Storage (Recommended)

For server-side applications, store tokens in encrypted form:

```javascript
const crypto = require('crypto');

class TokenManager {
  constructor(encryptionKey) {
    this.encryptionKey = encryptionKey;
    this.algorithm = 'aes-256-gcm';
  }
  
  encrypt(text) {
    const iv = crypto.randomBytes(16);
    const cipher = crypto.createCipher(this.algorithm, this.encryptionKey, iv);
    
    let encrypted = cipher.update(text, 'utf8', 'hex');
    encrypted += cipher.final('hex');
    
    const authTag = cipher.getAuthTag();
    
    return {
      encrypted,
      iv: iv.toString('hex'),
      authTag: authTag.toString('hex')
    };
  }
  
  decrypt(encryptedData) {
    const decipher = crypto.createDecipher(
      this.algorithm, 
      this.encryptionKey, 
      Buffer.from(encryptedData.iv, 'hex')
    );
    
    decipher.setAuthTag(Buffer.from(encryptedData.authTag, 'hex'));
    
    let decrypted = decipher.update(encryptedData.encrypted, 'hex', 'utf8');
    decrypted += decipher.final('utf8');
    
    return decrypted;
  }
  
  async storeTokens(userId, tokens) {
    const tokenData = {
      accessToken: tokens.access_token,
      refreshToken: tokens.refresh_token,
      idToken: tokens.id_token,
      expiresAt: new Date(Date.now() + tokens.expires_in * 1000),
      tokenType: tokens.token_type || 'Bearer',
      scope: tokens.scope
    };
    
    // Encrypt sensitive tokens
    const encryptedData = {
      accessToken: this.encrypt(tokenData.accessToken),
      refreshToken: this.encrypt(tokenData.refreshToken),
      idToken: this.encrypt(tokenData.idToken),
      expiresAt: tokenData.expiresAt,
      tokenType: tokenData.tokenType,
      scope: tokenData.scope
    };
    
    // Store in database
    await database.saveUserTokens(userId, encryptedData);
  }
  
  async getTokens(userId) {
    const encryptedData = await database.getUserTokens(userId);
    if (!encryptedData) return null;
    
    return {
      accessToken: this.decrypt(encryptedData.accessToken),
      refreshToken: this.decrypt(encryptedData.refreshToken),
      idToken: this.decrypt(encryptedData.idToken),
      expiresAt: encryptedData.expiresAt,
      tokenType: encryptedData.tokenType,
      scope: encryptedData.scope
    };
  }
}

// Usage
const tokenManager = new TokenManager(process.env.TOKEN_ENCRYPTION_KEY);
```

### Client-Side Storage (SPAs)

For Single Page Applications, use secure browser storage:

```javascript
class ClientTokenManager {
  constructor() {
    this.storage = window.sessionStorage; // More secure than localStorage
    this.tokenKey = 'st_tokens';
  }
  
  storeTokens(tokens) {
    const tokenData = {
      accessToken: tokens.access_token,
      refreshToken: tokens.refresh_token,
      idToken: tokens.id_token,
      expiresAt: Date.now() + (tokens.expires_in * 1000),
      tokenType: tokens.token_type || 'Bearer',
      storedAt: Date.now()
    };
    
    this.storage.setItem(this.tokenKey, JSON.stringify(tokenData));
  }
  
  getTokens() {
    const stored = this.storage.getItem(this.tokenKey);
    if (!stored) return null;
    
    try {
      const tokenData = JSON.parse(stored);
      
      // Check if tokens are expired
      if (Date.now() >= tokenData.expiresAt) {
        this.clearTokens();
        return null;
      }
      
      return tokenData;
    } catch (error) {
      console.error('Error parsing stored tokens:', error);
      this.clearTokens();
      return null;
    }
  }
  
  clearTokens() {
    this.storage.removeItem(this.tokenKey);
  }
  
  isAuthenticated() {
    const tokens = this.getTokens();
    return tokens && tokens.accessToken;
  }
}
```

## Automatic Token Refresh

### Server-Side Token Refresh

```javascript
class TokenRefreshManager extends TokenManager {
  constructor(encryptionKey, clientConfig) {
    super(encryptionKey);
    this.clientConfig = clientConfig;
    this.refreshBuffer = 5 * 60 * 1000; // Refresh 5 minutes before expiry
  }
  
  async getValidAccessToken(userId) {
    const tokens = await this.getTokens(userId);
    if (!tokens) {
      throw new Error('No tokens found - user needs to re-authenticate');
    }
    
    // Check if token needs refresh
    const now = new Date();
    const expiryWithBuffer = new Date(tokens.expiresAt.getTime() - this.refreshBuffer);
    
    if (now >= expiryWithBuffer) {
      console.log('Access token expired or expiring soon, refreshing...');
      
      try {
        const newTokens = await this.refreshAccessToken(tokens.refreshToken);
        await this.storeTokens(userId, newTokens);
        return newTokens.access_token;
      } catch (error) {
        console.error('Token refresh failed:', error);
        
        // Clear invalid tokens
        await this.clearUserTokens(userId);
        throw new Error('Token refresh failed - user needs to re-authenticate');
      }
    }
    
    return tokens.accessToken;
  }
  
  async refreshAccessToken(refreshToken) {
    const tokenRequest = {
      grant_type: 'refresh_token',
      refresh_token: refreshToken,
      client_id: this.clientConfig.clientId
    };
    
    // Add client secret for confidential clients
    if (this.clientConfig.clientSecret) {
      tokenRequest.client_secret = this.clientConfig.clientSecret;
    }
    
    const response = await fetch('https://account.oten.com/v1/oauth/token', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Accept': 'application/json'
      },
      body: new URLSearchParams(tokenRequest)
    });
    
    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(`Token refresh failed: ${errorData.error_description || errorData.error}`);
    }
    
    const newTokens = await response.json();
    
    // Some implementations return new refresh tokens
    if (!newTokens.refresh_token) {
      newTokens.refresh_token = refreshToken;
    }
    
    return newTokens;
  }
  
  async clearUserTokens(userId) {
    await database.deleteUserTokens(userId);
  }
}
```

### Client-Side Token Refresh

```javascript
class ClientTokenRefreshManager extends ClientTokenManager {
  constructor() {
    super();
    this.refreshPromise = null; // Prevent concurrent refresh attempts
  }
  
  async getValidAccessToken() {
    const tokens = this.getTokens();
    if (!tokens) {
      throw new Error('No tokens found');
    }
    
    // Check if token needs refresh (5 minutes buffer)
    const now = Date.now();
    const expiryWithBuffer = tokens.expiresAt - (5 * 60 * 1000);
    
    if (now >= expiryWithBuffer) {
      return await this.refreshTokens();
    }
    
    return tokens.accessToken;
  }
  
  async refreshTokens() {
    // Prevent concurrent refresh attempts
    if (this.refreshPromise) {
      return await this.refreshPromise;
    }
    
    this.refreshPromise = this._performRefresh();
    
    try {
      const result = await this.refreshPromise;
      return result;
    } finally {
      this.refreshPromise = null;
    }
  }
  
  async _performRefresh() {
    const tokens = this.getTokens();
    if (!tokens || !tokens.refreshToken) {
      throw new Error('No refresh token available');
    }
    
    try {
      const response = await fetch('/api/refresh-token', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          refresh_token: tokens.refreshToken
        })
      });
      
      if (!response.ok) {
        throw new Error('Token refresh failed');
      }
      
      const newTokens = await response.json();
      this.storeTokens(newTokens);
      
      return newTokens.access_token;
    } catch (error) {
      console.error('Token refresh failed:', error);
      this.clearTokens();
      
      // Redirect to login
      window.location.href = '/login';
      throw error;
    }
  }
}
```

## 🌐 Using Tokens for API Calls

### HTTP Client with Automatic Token Refresh

```javascript
class AuthenticatedHttpClient {
  constructor(tokenManager, userId) {
    this.tokenManager = tokenManager;
    this.userId = userId;
  }
  
  async request(url, options = {}) {
    try {
      // Get valid access token
      const accessToken = await this.tokenManager.getValidAccessToken(this.userId);
      
      // Add authorization header
      const headers = {
        'Authorization': `Bearer ${accessToken}`,
        'Content-Type': 'application/json',
        ...options.headers
      };
      
      const response = await fetch(url, {
        ...options,
        headers
      });
      
      // Handle token expiration
      if (response.status === 401) {
        console.log('Received 401, attempting token refresh...');
        
        // Try to refresh token and retry once
        const newAccessToken = await this.tokenManager.refreshAccessToken();
        
        const retryResponse = await fetch(url, {
          ...options,
          headers: {
            ...headers,
            'Authorization': `Bearer ${newAccessToken}`
          }
        });
        
        return retryResponse;
      }
      
      return response;
    } catch (error) {
      console.error('API request failed:', error);
      throw error;
    }
  }
  
  async get(url, options = {}) {
    return this.request(url, { ...options, method: 'GET' });
  }
  
  async post(url, data, options = {}) {
    return this.request(url, {
      ...options,
      method: 'POST',
      body: JSON.stringify(data)
    });
  }
  
  async put(url, data, options = {}) {
    return this.request(url, {
      ...options,
      method: 'PUT',
      body: JSON.stringify(data)
    });
  }
  
  async delete(url, options = {}) {
    return this.request(url, { ...options, method: 'DELETE' });
  }
}

// Usage
const httpClient = new AuthenticatedHttpClient(tokenManager, userId);

// Make authenticated API calls
const userProfile = await httpClient.get('/api/user/profile');
const updateResult = await httpClient.put('/api/user/profile', { name: 'New Name' });
```

### Axios Interceptor Example

```javascript
import axios from 'axios';

function setupAxiosInterceptors(tokenManager, userId) {
  // Request interceptor to add auth header
  axios.interceptors.request.use(async (config) => {
    try {
      const accessToken = await tokenManager.getValidAccessToken(userId);
      config.headers.Authorization = `Bearer ${accessToken}`;
    } catch (error) {
      console.error('Failed to get access token:', error);
      // Redirect to login or handle error
    }
    
    return config;
  });
  
  // Response interceptor to handle token expiration
  axios.interceptors.response.use(
    (response) => response,
    async (error) => {
      const originalRequest = error.config;
      
      if (error.response?.status === 401 && !originalRequest._retry) {
        originalRequest._retry = true;
        
        try {
          const newAccessToken = await tokenManager.refreshAccessToken();
          originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
          
          return axios(originalRequest);
        } catch (refreshError) {
          console.error('Token refresh failed:', refreshError);
          // Redirect to login
          window.location.href = '/login';
        }
      }
      
      return Promise.reject(error);
    }
  );
}
```

## Token Lifecycle Management

### Token Cleanup and Rotation

```javascript
class TokenLifecycleManager extends TokenRefreshManager {
  constructor(encryptionKey, clientConfig) {
    super(encryptionKey, clientConfig);
    this.maxTokenAge = 30 * 24 * 60 * 60 * 1000; // 30 days
  }
  
  async cleanupExpiredTokens() {
    const expiredBefore = new Date(Date.now() - this.maxTokenAge);
    await database.deleteExpiredTokens(expiredBefore);
    console.log('Cleaned up expired tokens');
  }
  
  async revokeTokens(userId) {
    const tokens = await this.getTokens(userId);
    if (!tokens) return;
    
    try {
      // Revoke refresh token at IDP
      await this.revokeRefreshToken(tokens.refreshToken);
    } catch (error) {
      console.error('Failed to revoke token at IDP:', error);
    }
    
    // Clear local tokens
    await this.clearUserTokens(userId);
  }
  
  async revokeRefreshToken(refreshToken) {
    const response = await fetch('https://account.oten.com/v1/oauth/revoke', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      body: new URLSearchParams({
        token: refreshToken,
        token_type_hint: 'refresh_token',
        client_id: this.clientConfig.clientId,
        client_secret: this.clientConfig.clientSecret
      })
    });
    
    if (!response.ok) {
      throw new Error('Token revocation failed');
    }
  }
  
  // Schedule periodic cleanup
  startCleanupSchedule() {
    setInterval(() => {
      this.cleanupExpiredTokens().catch(console.error);
    }, 24 * 60 * 60 * 1000); // Daily cleanup
  }
}
```

### Session Management Integration

```javascript
// Express.js session integration
app.use(async (req, res, next) => {
  if (req.session.userId) {
    try {
      // Ensure user has valid tokens
      const accessToken = await tokenManager.getValidAccessToken(req.session.userId);
      req.accessToken = accessToken;
      req.authenticated = true;
    } catch (error) {
      console.log('Token validation failed, clearing session');
      req.session.destroy();
      req.authenticated = false;
    }
  } else {
    req.authenticated = false;
  }
  
  next();
});

// Logout handler
app.post('/logout', async (req, res) => {
  if (req.session.userId) {
    try {
      await tokenManager.revokeTokens(req.session.userId);
    } catch (error) {
      console.error('Token revocation failed:', error);
    }
    
    req.session.destroy();
  }
  
  res.redirect('/');
});
```

## Monitoring and Metrics

### Token Usage Metrics

```javascript
class TokenMetrics {
  static recordTokenRefresh(success, error = null) {
    if (success) {
      metrics.increment('tokens.refresh.success');
    } else {
      metrics.increment('tokens.refresh.failure', { error: error?.message });
    }
  }
  
  static recordTokenUsage(endpoint, responseStatus) {
    metrics.increment('tokens.usage', {
      endpoint,
      status: responseStatus
    });
  }
  
  static recordTokenExpiration(userId) {
    metrics.increment('tokens.expired', { user_id: userId });
  }
}
```

## Token Management Checklist

Ensure your token management:

* [ ] Stores tokens securely (encrypted at rest)
* [ ] Implements automatic token refresh
* [ ] Handles token expiration gracefully
* [ ] Uses tokens correctly in API calls
* [ ] Implements proper cleanup and revocation
* [ ] Monitors token usage and refresh rates
* [ ] Handles concurrent refresh attempts
* [ ] Provides fallback for refresh failures

***

## Navigation

* **← Previous**: [Step 4: Handle Callback](broken://pages/ptByYQGxY6FoBjvmWxhC) - Process authorization response
* **↑ Overview**: [Integration Flow Overview](broken://pages/iMMrTD1lQOMbxxvheKAz) - See the big picture
* **Complete**: You've finished all integration steps!

**Progress**: Step 5 of 5 complete ✅

## Congratulations!

You've successfully completed the Oten IDP integration! Here's what you've accomplished:

✅ **Prerequisites** - Set up your environment and registered with Oten ✅ **JAR Implementation** - Understood and implemented JWT-Secured Authorization Request ✅ **Library Selection** - Chose the right OAuth library for your stack ✅ **Client Configuration** - Set up your OAuth client with proper endpoints ✅ **Authorization Flow** - Implemented JAR-based authorization with PKCE ✅ **Callback Handling** - Processed OAuth callbacks and exchanged codes for tokens ✅ **Token Management** - Implemented secure token storage and refresh

## 🚀 What's Next?

### Production Readiness

* [**Security Best Practices**](broken://pages/9YmJ1SYdIDChDe4DceZt) - Harden your implementation
* [**Testing Guide**](broken://pages/nCS6xWd0DIen0JQv5bWZ) - Comprehensive testing strategies
* [**Monitoring & Logging**](https://gitlab.silvertiger.tech/documents/idp/-/blob/main/best-practices/monitoring-logging.md) - Production monitoring

### Advanced Features

* [**Error Handling**](broken://pages/44IExs5hg9FRrZO0yuwc) - Robust error handling
* [**User Experience**](broken://pages/VugqfwOcI6hhI1kqMdw8) - Optimize user flows

### Support

* [**Common Errors**](broken://pages/ChQqcK8cqX9LLgyQtBJT) - Troubleshooting guide
* [**Contact Support**](https://gitlab.silvertiger.tech/documents/idp/-/blob/main/support/contact-support.md) - Get help when needed

**Happy coding!**
