OAuth 2.0 Authentication
��� Authentication Method
OAuth 2.0 Authorization Code Flow with automatic token refresh
��� Required Configuration
Environment Variables:
| Variable | Description | Required |
|---|---|---|
GOOGLE_ANALYTICS_CLIENT_ID | OAuth 2.0 Client ID | ✅ |
GOOGLE_ANALYTICS_CLIENT_SECRET | OAuth 2.0 Client Secret | ✅ |
GOOGLE_ANALYTICS_CALLBACK_URL | OAuth callback URL (production) | ✅ |
PROXY_URL | OAuth callback URL (development) | ✅ |
APP_SECRET | JWT secret for state token encryption | ✅ |
NODE_ENV | Environment (development/production) | ✅ |
OAuth Scopes:
https://www.googleapis.com/auth/analytics- Full Analytics accesshttps://www.googleapis.com/auth/analytics.readonly- Read-only accessprofile- User profile informationemail- User email address
��� API Endpoints
| Endpoint | Method | Description |
|---|---|---|
/v1/integrations/google/analytics/auth/login | GET | Initiate OAuth flow |
/v1/integrations/google/analytics/auth/callback | GET | OAuth callback endpoint |
/v1/integrations/google/analytics/auth/useranalyticsconfig | POST | Save user analytics config |
/v1/integrations/google/analytics/auth | DELETE | Disconnect integration |
��� OAuth Flow
1. Initiate OAuth
Endpoint: GET /v1/integrations/google/analytics/auth/login
Query Parameters:
forward_url(required) - Redirect URL after OAuth
Process:
- Delete existing token (force re-auth)
- Create JWT state token with
account_id,forward_url,created_by - Generate Google OAuth URL
- Redirect user to Google consent screen
Example:
GET /v1/integrations/google/analytics/auth/login?forward_url=https://app.dashclicks.com/analytics
Authorization: Bearer {jwt_token}
Redirects to:
https://accounts.google.com/o/oauth2/v2/auth?
client_id=123456789-abc.apps.googleusercontent.com&
redirect_uri=https://api.dashclicks.com/v1/e/google/analytics/auth/callback&
response_type=code&
scope=analytics%20profile%20email&
access_type=offline&
prompt=consent&
state=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
2. OAuth Callback
Endpoint: GET /v1/integrations/google/analytics/auth/callback
Query Parameters:
code(required) - Authorization codestate(required) - JWT state token
Process:
- Verify JWT state token
- Exchange authorization code for tokens
- Call Google People API to get user email
- Save token in
google.analytics.tokenscollection - Redirect to
forward_urlwith status
Token Response:
{
access_token: "ya29.a0AfH6SMB...",
refresh_token: "1//0gZ1X2Y3Z4...",
expiry_date: 1704844800000,
id_token: "eyJhbGciOiJSUzI1NiIsImtpZCI6...",
scope: "https://www.googleapis.com/auth/analytics email profile",
token_type: "Bearer"
}
Redirect URLs:
# Success
https://app.dashclicks.com/analytics?status=success&integration=google&token={document_id}
# Error
https://app.dashclicks.com/analytics?status=error&integration=google&reason={error_message}
3. Save User Configuration
Endpoint: POST /v1/integrations/google/analytics/auth/useranalyticsconfig
Request:
{
"accountid": "167475709",
"version": "v3",
"propertyid": "UA-167475709-1",
"viewid": "219224050"
}
Process:
- Verify account access
- Update token with version
- Upsert user config
- Send FCM notification
Response:
{
"success": true,
"message": "SUCCESS",
"data": {
"acknowledged": true,
"modifiedCount": 1
}
}
4. Disconnect
Endpoint: DELETE /v1/integrations/google/analytics/auth
Process:
- Delete user config
- Soft delete token (set
deleteddate) - Send email and FCM notifications
Response:
{
"success": true,
"message": "SUCCESS"
}
⚡ Token Management
Automatic Token Refresh
Purpose: Ensure valid access token for every API call
Implementation:
const checkAccessTokenValidity = async (expirationTime, accessToken, refreshToken, docId) => {
const currentTimeStamp = new Date().getTime();
if (currentTimeStamp > expirationTime) {
// Token expired - refresh it
const tokenData = await provider.getNewAccessToken(refreshToken);
const newTokenData = {
access_token: tokenData.access_token,
expiry_date: tokenData.expires_in * 1000 + currentTimeStamp,
refresh_token: refreshToken,
id_token: tokenData.id_token,
scope: tokenData.scope,
token_type: tokenData.token_type,
};
await keysModel.update({ _id: docId }, { token: newTokenData });
return tokenData.access_token;
}
return accessToken;
};
Called Before Every API Request:
const accessToken = await checkAccessTokenValidity(
token.expiry_date,
token.access_token,
token.refresh_token,
docId,
);
Token Invalidation
Triggers:
401 Unauthorizedfrom Google API403 Forbiddenfrom Google APIinvalid_granterror
Process:
if (error?.response?.data?.error == 'invalid_grant' || errorCode == '401' || errorCode == '403') {
await keysModel.updateMany(
{ account_id: ObjectId(accountId) },
{ $set: { token_invalidated: true } },
);
throw {
message:
'Google Analytics token is expired. Please reconnect your analytics account in settings.',
additional_info: 'TOKEN_INVALIDATED',
};
}
��� Keys Model
Purpose: Manage Google Analytics OAuth tokens
Methods:
// Find token by account
Keys.find(accountID, owner);
// Returns: { docId, data: token_document } or false
// Save new token
Keys.save(tokenData);
// Returns: Saved document with id
// Update token
Keys.update(condition, authData);
// Returns: Updated document
// Update many (for invalidation)
Keys.updateMany(condition, updateData);
// Returns: MongoDB update result
// Delete token
Keys.delete(docId);
// Returns: true on success
��� Notifications
Integration Connected
Channels: FCM/Bell, Browser push
Notification:
{
title: "Google Analytics integration added",
body: "A new Google Analytics integration has been added.",
module: "projects",
type: "project_added",
data: { subType: "bell" }
}
Integration Disconnected
Channels: FCM/Bell, Email
Notification:
{
title: "Google Analytics integration is Disconnected",
body: "Google Analytics integration has been disconnected.",
module: "analytics",
type: "integration_disconnected",
click_action: "https://app.dashclicks.com/analytics",
data: { subType: "bell" }
}
Email:
{
subject: "Google Analytics integration disconnected",
content: "Google Analytics integration has been disconnected.",
origin: "analytics",
recipients: [{
name: user.name,
email: user.email
}]
}
��� Error Handling
| Error | Cause | Handling |
|---|---|---|
| 400 forward_url required | Missing parameter | Return error before OAuth |
| 400 Record Not found | No token in database | User must authenticate |
| 403 Invalid State | JWT token invalid | Retry OAuth flow |
| 401/403 from Google | Token expired/revoked | Set token_invalidated flag |
| invalid_grant | Refresh token revoked | Prompt re-authentication |
⚠️ Important Notes
- ��� State Token Security: JWT-encrypted to prevent CSRF attacks
- ��� Automatic Refresh: Access tokens renewed before expiration
- ���️ Soft Delete: Tokens soft-deleted for audit trail
- ��� Force Re-auth: Login endpoint always deletes existing token
- ��� Email Retrieval: Uses Google People API
- ��� Notification Control: Respects user preferences
- ��� Sub-account Support: Works with DashClicks sub-accounts
- ⚡ Fast Checks: Token validity checked in less than 1ms