Centralized_Reporting_Backend/docs/TOKEN_REFRESH.md
2025-10-10 12:10:33 +05:30

437 lines
9.8 KiB
Markdown

# Token Refresh Mechanism
## Overview
The n8n integration includes automatic token refresh for both Zoho and Salesforce to ensure uninterrupted API access.
---
## How It Works
### Two-Level Token Refresh Strategy
#### 1. **Proactive Refresh** (Before Token Expires)
- Checks token expiration before every API call
- Refreshes token if it expires within **5 minutes**
- Prevents 401 errors before they happen
#### 2. **Reactive Refresh** (On 401 Error)
- If API returns 401 (Unauthorized), automatically refreshes token
- Retries the API call once with the new token
- Handles cases where token expired during the request
---
## Zoho Token Refresh
### Configuration Required
```env
ZOHO_CLIENT_ID=your_zoho_client_id
ZOHO_CLIENT_SECRET=your_zoho_client_secret
```
### Refresh Process
```javascript
// 1. Check if token is expired
const isExpired = expiresAt && new Date(expiresAt) <= new Date(Date.now() + 5 * 60 * 1000);
if (isExpired) {
// 2. Call Zoho token refresh API
POST https://accounts.zoho.com/oauth/v2/token
Body:
refresh_token: xxx
client_id: xxx
client_secret: xxx
grant_type: refresh_token
// 3. Receive new access token
Response: {
access_token: "new_token",
expires_in: 3600
}
// 4. Update database with new token
// 5. Use new token for API call
}
```
### Token Lifetime
- **Access Token**: 1 hour (3600 seconds)
- **Refresh Token**: Does not expire (unless revoked)
- **Proactive Refresh**: 5 minutes before expiry
---
## Salesforce Token Refresh
### Configuration Required
```env
SALESFORCE_CLIENT_ID=your_salesforce_client_id
SALESFORCE_CLIENT_SECRET=your_salesforce_client_secret
SALESFORCE_INSTANCE_URL=https://login.salesforce.com # or https://test.salesforce.com for sandbox
```
### Refresh Process
```javascript
// 1. Check if token is expired
const isExpired = expiresAt && new Date(expiresAt) <= new Date(Date.now() + 5 * 60 * 1000);
if (isExpired) {
// 2. Call Salesforce token refresh API
POST https://login.salesforce.com/services/oauth2/token
Body:
grant_type: refresh_token
refresh_token: xxx
client_id: xxx
client_secret: xxx
// 3. Receive new access token
Response: {
access_token: "new_token",
instance_url: "https://yourinstance.salesforce.com"
}
// 4. Update database with new token and instance URL
// 5. Use new token for API call
}
```
### Token Lifetime
- **Access Token**: 2 hours (estimated, Salesforce doesn't return expires_in on refresh)
- **Refresh Token**: Does not expire (unless revoked or password changed)
- **Proactive Refresh**: 5 minutes before expiry
---
## Implementation Details
### File: `src/integrations/n8n/handler.js`
#### 1. Get Service Tokens
```javascript
async getServiceTokens(serviceName) {
const tokenData = await userAuthTokenRepo.findByUserAndService(this.userId, serviceName);
return {
accessToken: decrypt(tokenData.accessToken),
refreshToken: decrypt(tokenData.refreshToken),
instanceUrl: tokenData.instanceUrl,
expiresAt: tokenData.expiresAt // ← Used to check expiration
};
}
```
#### 2. Refresh Zoho Token
```javascript
async refreshZohoToken() {
const { refreshToken } = await this.getServiceTokens('zoho');
// Call Zoho refresh API
const response = await axios.post('https://accounts.zoho.com/oauth/v2/token', params);
const { access_token, expires_in } = response.data;
// Update database
await userAuthTokenRepo.upsertToken({
userUuid: this.userId,
serviceName: 'zoho',
accessToken: encrypt(access_token),
refreshToken: encrypt(refreshToken),
expiresAt: new Date(Date.now() + expires_in * 1000)
});
return access_token;
}
```
#### 3. Refresh Salesforce Token
```javascript
async refreshSalesforceToken() {
const { refreshToken, instanceUrl } = await this.getServiceTokens('salesforce');
// Call Salesforce refresh API
const response = await axios.post(`${tokenUrl}/services/oauth2/token`, params);
const { access_token, instance_url } = response.data;
// Update database
await userAuthTokenRepo.upsertToken({
userUuid: this.userId,
serviceName: 'salesforce',
accessToken: encrypt(access_token),
refreshToken: encrypt(refreshToken),
instanceUrl: instance_url || instanceUrl,
expiresAt: new Date(Date.now() + 2 * 60 * 60 * 1000) // 2 hours
});
return access_token;
}
```
#### 4. Fetch with Auto-Refresh
```javascript
async fetchZohoData(service, module, options) {
try {
let { accessToken, expiresAt } = await this.getServiceTokens('zoho');
// Proactive refresh (check expiration)
const isExpired = expiresAt && new Date(expiresAt) <= new Date(Date.now() + 5 * 60 * 1000);
if (isExpired) {
logger.info('Token expired, refreshing');
accessToken = await this.refreshZohoToken();
}
// Make API call
const result = await this.client.fetchZohoData(service, module, accessToken, query);
return this.normalizeResponse(result, 'zoho');
} catch (error) {
// Reactive refresh (on 401 error)
if (error.message.includes('401')) {
logger.info('Received 401, refreshing token');
const newAccessToken = await this.refreshZohoToken();
const result = await this.client.fetchZohoData(service, module, newAccessToken, query);
return this.normalizeResponse(result, 'zoho');
}
throw error;
}
}
```
---
## Flow Diagram
### Successful Request (Token Valid)
```
User Request
Check Token Expiration
Token Valid (> 5 min remaining)
Fetch Data from n8n
Return Data to User
```
### Proactive Refresh (Token Expiring Soon)
```
User Request
Check Token Expiration
Token Expiring (< 5 min remaining)
Refresh Token
Update Database
Fetch Data with New Token
Return Data to User
```
### Reactive Refresh (401 Error)
```
User Request
Check Token Expiration
Token Appears Valid
Fetch Data from n8n
Receive 401 Error
Refresh Token
Update Database
Retry Fetch Data
Return Data to User
```
---
## Database Schema
### user_auth_tokens Table
```sql
CREATE TABLE user_auth_tokens (
id INT PRIMARY KEY,
user_uuid CHAR(36),
service_name ENUM('zoho', 'salesforce', ...),
access_token TEXT, -- Encrypted
refresh_token TEXT, -- Encrypted (used for refresh)
instance_url VARCHAR(255), -- Salesforce only
expires_at DATETIME, -- Used to check if refresh needed
created_at DATETIME,
updated_at DATETIME
);
```
---
## Logging
### Token Refresh Logs
```
// Before refresh
INFO: Zoho token expired or expiring soon, refreshing { userId: 'xxx' }
INFO: Refreshing Zoho token { userId: 'xxx' }
// After successful refresh
INFO: Zoho token refreshed successfully { userId: 'xxx' }
// On 401 error
INFO: Received 401 error, attempting token refresh { userId: 'xxx' }
INFO: Refreshing Zoho token { userId: 'xxx' }
INFO: Zoho token refreshed successfully { userId: 'xxx' }
// On failure
ERROR: Failed to refresh Zoho token { userId: 'xxx', error: '...' }
```
---
## Error Handling
### No Refresh Token Available
```javascript
if (!refreshToken) {
throw new Error('No Zoho refresh token available. Please re-authenticate.');
}
```
**User Action Required**: Re-authenticate via OAuth
### Refresh Token Expired/Revoked
```javascript
catch (error) {
throw new Error(`Zoho token refresh failed: ${error.message}`);
}
```
**Possible Causes**:
- User revoked access
- Refresh token expired (rare)
- OAuth app credentials changed
- Password changed (Salesforce)
**User Action Required**: Re-authenticate via OAuth
---
## Best Practices
### 1. **5-Minute Buffer**
- Refreshes 5 minutes before expiry
- Prevents race conditions
- Accounts for clock skew
### 2. **Single Retry**
- Only retries once after token refresh
- Prevents infinite loops
- Fails fast if refresh doesn't work
### 3. **Encrypted Storage**
- All tokens encrypted in database
- Refresh tokens never logged
- Secure token transmission
### 4. **Database Updates**
- Updates `expires_at` after each refresh
- Keeps refresh token unchanged
- Updates instance URL (Salesforce)
---
## Testing Token Refresh
### Test Expired Token
```javascript
// Manually expire token in database
UPDATE user_auth_tokens
SET expires_at = NOW()
WHERE user_uuid = 'your-user-id' AND service_name = 'zoho';
// Make API call - should auto-refresh
curl -X GET "http://localhost:3000/api/v1/n8n/zoho/crm/leads" \
-H "Authorization: Bearer YOUR_JWT"
```
**Expected Logs**:
```
INFO: Zoho token expired or expiring soon, refreshing
INFO: Refreshing Zoho token
INFO: Zoho token refreshed successfully
INFO: Fetching Zoho data via n8n
```
### Test 401 Error Handling
```javascript
// Use invalid token
UPDATE user_auth_tokens
SET access_token = 'invalid_encrypted_token'
WHERE user_uuid = 'your-user-id' AND service_name = 'salesforce';
// Make API call - should catch 401 and refresh
curl -X GET "http://localhost:3000/api/v1/n8n/salesforce/crm/leads" \
-H "Authorization: Bearer YOUR_JWT"
```
**Expected Logs**:
```
INFO: Fetching Salesforce data via n8n
ERROR: n8n webhook call failed (401)
INFO: Received 401 error, attempting token refresh
INFO: Refreshing Salesforce token
INFO: Salesforce token refreshed successfully
INFO: Fetching Salesforce data via n8n (retry)
```
---
## Troubleshooting
### Issue: "No refresh token available"
**Cause**: User authenticated without requesting refresh token
**Solution**:
1. Check OAuth scope includes refresh token
2. Re-authenticate user via `/oauth/callback`
### Issue: "Token refresh failed"
**Cause**: Refresh token invalid or expired
**Solution**:
1. User must re-authenticate
2. Check OAuth app credentials in `.env`
### Issue: Token refreshes but still gets 401
**Cause**: API issue or wrong credentials
**Solution**:
1. Check provider API status
2. Verify OAuth app has correct permissions
3. Check instance URL (Salesforce)
---
**Last Updated**: October 9, 2025
**Version**: 1.0.0