🔐 TikTok Ads - Authentication
📖 Overview
TikTok Marketing API uses OAuth 2.0 Authorization Code Flow for secure authentication. This implementation handles the complete OAuth flow, token storage in MongoDB, automatic token invalidation detection, and advertiser account configuration.
Source Files:
- Controller:
external/Integrations/TikTok/Controllers/auth.js - Model:
external/Integrations/TikTok/Models/keys.js - Provider:
external/Integrations/TikTok/providers/tiktok-analytics.js
External API: TikTok Marketing API OAuth endpoints
🗄️ Collections Used
tiktok.tokens
- Operations: Create, Read, Update, Delete
- Model:
shared/models/tik-tok-token.js - Usage Context: Store OAuth 2.0 access tokens with account association
analytics_tiktokanalytics_userconfigs
- Operations: Create, Update, Read
- Model:
external/models/analytics-tiktokanalytics-userconfig.js - Usage Context: Store selected advertiser account configuration
🔄 OAuth 2.0 Flow
sequenceDiagram
participant User as User Browser
participant DC as DashClicks API
participant JWT as JWT Service
participant DB as MongoDB
participant TikTok as TikTok OAuth
User->>DC: GET /auth/login?forward_url=...
DC->>DB: Check existing token
alt Token Exists
DC->>User: Redirect to forward_url (success)
else No Token
DC->>JWT: Sign state token
JWT-->>DC: JWT with account/user info
DC->>User: Redirect to TikTok OAuth
User->>TikTok: Authorize app
TikTok-->>User: Redirect with auth_code
User->>DC: GET /callback?auth_code=...&state=...
DC->>JWT: Verify state token
DC->>TikTok: POST /oauth2/access_token
TikTok-->>DC: Access token response
DC->>DB: Save token + account association
DC->>User: Redirect to forward_url (success)
end
🔑 Environment Variables
| Variable | Description | Example |
|---|---|---|
TIK_TOK_CLIENT_ID | TikTok app ID from developer portal | abc123... |
TIKTOK_CLIENT_SECRET | TikTok app secret | secret123... |
TIK_TOK_REDIRECT_URL | OAuth callback URL (must match portal) | https://api.dashclicks.com/v1/integrations/tiktok/auth/callback |
APP_SECRET | JWT signing secret for state token | Your secure secret |
TIKTOK_BASE_URL | TikTok Marketing API base URL | https://business-api.tiktok.com |
TIKTOK_VERSION | API version | v1.3 |
🔧 Authentication Functions
Step 1: Initiate OAuth Flow
loginAuth(req, res, next)
Purpose: Start OAuth 2.0 authorization flow by redirecting user to TikTok authorization page
Source: Controllers/auth.js
External API Endpoint: https://ads.tiktok.com/marketing_api/auth
Request Parameters:
GET /v1/integrations/tiktok/auth/login?forward_url=https://app.dashclicks.com/integrations
Authorization: Bearer {jwt_token}
| Parameter | Type | Required | Description |
|---|---|---|---|
forward_url | String | ✅ | URL to redirect after OAuth completion |
Business Logic Flow:
-
Validate Forward URL
- Check if
forward_urlquery parameter exists - Return 400 error if missing
- Check if
-
Check Account Access
- Call
checkAccountAccess(req)utility - Extract account ID and creator ID from JWT
- Return 400 error if invalid account
- Call
-
Delete Existing Token (Force Re-auth)
- Search for existing token by account ID
- If found, delete the token document
- Ignore errors if token doesn't exist (ensures fresh auth)
-
Generate JWT State Token
- Sign JWT containing:
account_id- DashClicks account identifierforward_url- Redirect destinationcreated_by- User who initiated connection
- Uses HS256 algorithm with
APP_SECRET - No explicit expiration (short-lived flow)
- Sign JWT containing:
-
Build TikTok Authorization URL
- Base:
https://ads.tiktok.com/marketing_api/auth - Query parameters:
app_id- FromTIK_TOK_CLIENT_IDenv vardisplay=popup- UI display modestate={jwt_token}- Signed state tokenresponse_type=code- OAuth code flowredirect_uri- FromTIK_TOK_REDIRECT_URLenv var
- Base:
-
Redirect User
- HTTP 301 redirect to TikTok authorization URL
- User sees TikTok authorization screen
TikTok Authorization URL Example:
https://ads.tiktok.com/marketing_api/auth
?app_id=1234567890
&display=popup
&state=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
&response_type=code
&redirect_uri=https://api.dashclicks.com/v1/integrations/tiktok/auth/callback
Success Response: 301 Redirect to TikTok
Error Response:
// If forward_url missing
{
"success": false,
"errno": 400,
"message": "forward url is required!"
}
// If account invalid
{
"success": false,
"message": "Invalid Account Id"
}
// On error with forward_url provided
301 Redirect to: {forward_url}?status=error&reason={error_message}&integration=tiktok
Error Handling:
- Logs token deletion errors (non-fatal)
- Redirects to
forward_urlwith error status on failure - Passes errors to Express error handler if no
forward_url
Side Effects:
- ⚠️ Deletes any existing TikTok token for the account
- ⚠️ Creates JWT state token
Example Usage:
// Initiate OAuth from frontend
window.location.href =
'https://api.dashclicks.com/v1/integrations/tiktok/auth/login?forward_url=' +
encodeURIComponent('https://app.dashclicks.com/integrations');
Step 2: OAuth Callback Handler
postAuth(req, res, next)
Purpose: Handle OAuth callback, exchange authorization code for access token, and store in MongoDB
Source: Controllers/auth.js
External API Endpoint: POST https://business-api.tiktok.com/v1.3/oauth2/access_token
Callback Request:
GET /v1/integrations/tiktok/auth/callback?code=...&auth_code=...&state={jwt_token}
| Parameter | Type | Description |
|---|---|---|
code | String | OAuth authorization code |
auth_code | String | TikTok authorization code (same as code) |
state | String | JWT state token from step 1 |
Business Logic Flow:
-
Verify State Token
- Verify JWT signature using
APP_SECRET - Extract account ID, forward URL, and creator ID
- Return 403 error if invalid state
- Verify JWT signature using
-
Parse State Parameters
- Decode JWT payload
- Extract URLSearchParams from
datafield - Get
account_id,forward_url,created_by
-
Check for Existing Token
- Call
keysModel.find(accountId) - If token exists, redirect immediately to success
- Continue if
KEYS_NOT_FOUNDerror
- Call
-
Validate Authorization Code
- Check if
codeorauth_codequery parameters exist - Return error redirect if missing
- Check if
-
Exchange Code for Access Token
- API Call:
tiktokAnalyticsProvider.getAccessToken() - HTTP Method: POST
- Endpoint:
{TIKTOK_BASE_URL}/{TIKTOK_VERSION}/oauth2/access_token - Headers:
Content-Type: application/json - Body:
{
"app_id": "{TIK_TOK_CLIENT_ID}",
"auth_code": "{auth_code}",
"secret": "{TIKTOK_CLIENT_SECRET}"
}
- API Call:
-
Save Token to MongoDB
- Create token document:
{
account_id: ObjectId(accountId),
token: {
data: {
access_token: "...",
expires_in: 86400,
token_type: "Bearer",
// ... other TikTok response fields
}
},
created_by: ObjectId(creatorId),
createdAt: Date,
updatedAt: Date
} - Call
keysModel.save(dataToSave) - Collection:
tiktok.tokens
- Create token document:
-
Redirect to Success URL
- Redirect to
forward_urlwith:status=successintegration=tiktoktoken={document_id}- MongoDB document ID
- Redirect to
TikTok Access Token API Request:
POST https://business-api.tiktok.com/v1.3/oauth2/access_token
Content-Type: application/json
{
"app_id": "1234567890",
"auth_code": "auth_code_from_callback",
"secret": "your_app_secret"
}
TikTok Access Token API Response:
{
"code": 0,
"message": "OK",
"data": {
"access_token": "act.1234567890abcdef...",
"advertiser_ids": ["1234567890"],
"expires_in": 86400, // 24 hours
"token_type": "Bearer"
}
}
Success Response: 301 Redirect
{forward_url}?status=success&integration=tiktok&token={mongodb_document_id}
Error Response: 301 Redirect
{forward_url}?status=error&reason={error_description}&integration=tiktok
Error Handling:
- Invalid State: Returns 403 JSON response
- Code Missing: Redirects with error message
- Token Exchange Failure: Redirects with TikTok error description
- Database Error: Redirects with error message
Side Effects:
- ⚠️ Creates new document in
tiktok.tokenscollection - ⚠️ Redirects user to success/error URL
Step 3: Save Advertiser Configuration
saveUserAnalyticsConfig(req, res, next)
Purpose: Associate a specific TikTok advertiser account with the DashClicks account for reporting
Source: Controllers/auth.js
Request:
POST /v1/integrations/tiktok/useranalyticsconfig
Authorization: Bearer {jwt_token}
Content-Type: application/json
{
"advertiser_id": "1234567890",
"advertiser_name": "My Business Account"
}
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
advertiser_id | String | ✅ | TikTok advertiser account ID |
advertiser_name | String | ✅ | Advertiser account display name |
Business Logic Flow:
-
Validate Account Access
- Get account ID via
checkAccountAccess(req) - Return 400 if invalid
- Get account ID via
-
Find Account Owner
- Query
Usercollection foris_owner: trueuser - Get owner's user ID
- Query
-
Get Notification Preferences
- Query
user-configcollection - Get
preferences.projects.notifications.project_addedsettings - Check for bell/browser/email notification preferences
- Query
-
Upsert Advertiser Configuration
- Collection:
analytics_tiktokanalytics_userconfigs - Operation:
updateOnewithupsert: true - Condition:
{ account_id: ObjectId(accountID) } - Data:
{
account_id: ObjectId(accountID),
advertiser_id: "1234567890",
advertiser_name: "My Business Account",
updatedAt: Date
}
- Collection:
-
Send Project Added Notification
- If notifications enabled in user config:
- FCM Notification:
{
title: "Tiktok integration added",
body: "A new Tiktok integration has been added.",
data: { subType: "bell" }, // if bell notifications enabled
module: "projects",
type: "project_added"
} - Schedule via
fcm.schedule([accountID], [user_id], notification) - Logs error if notification fails (non-fatal)
Success Response:
{
"success": true,
"message": "SUCCESS",
"data": {
// updateOne result
"acknowledged": true,
"modifiedCount": 1,
"upsertedId": null
}
}
Error Response:
{
"success": false,
"message": "Invalid Account Id"
}
Error Handling:
- Validates account ID before proceeding
- Logs notification errors (doesn't fail request)
- Returns 400 status code on errors
Side Effects:
- ⚠️ Upserts document in
analytics_tiktokanalytics_userconfigs - ⚠️ Sends FCM notification to account owner
- ⚠️ Logs notification errors
Step 4: Delete Integration
deleteDocument(req, res, next)
Purpose: Disconnect TikTok integration by marking token as deleted and removing advertiser config
Source: Controllers/auth.js
Request:
DELETE /v1/integrations/tiktok/auth
Authorization: Bearer {jwt_token}
Business Logic Flow:
-
Validate Account Access
- Get account ID via
checkAccountAccess(req) - Return 400 if invalid
- Get account ID via
-
Get User & Notification Preferences
- Find user by
req.auth.uid - Get user config for analytics notifications
- Check
preferences.analytics.notifications.integration_disconnected - Get active domain for notification links
- Find user by
-
Find Existing Token
- Call
keysModel.find(accountId) - Return 400 if no token found
- Call
-
Delete Advertiser Config
- Find config in
analytics_tiktokanalytics_userconfigs - If exists, delete via
deleteOne({ _id: config._id })
- Find config in
-
Send Disconnection Notifications
- If notifications enabled:
- FCM Notification:
{
title: "Tiktok integration is Disconnected",
body: "Tiktok integration has been disconnected.",
data: { subType: "bell" }, // if bell enabled
module: "analytics",
type: "integration_disconnected",
click_action: "{domainName}/analytics"
} - Email Notification (if enabled):
{
subject: "Tiktok integration disconnected",
content: "Tiktok integration has been disconnected.",
origin: "analytics",
recipients: [{
name: user.name,
email: user.email,
first_name: user.first_name,
last_name: user.last_name
}]
}
-
Notify Parent Account (if sub-account)
- If
req.auth.parent_account != accountId - Send same notifications to parent account
- Subject: "Tiktok integration for project disconnected"
- If
-
Soft Delete Token
- Collection:
tiktok.tokens - Operation:
updateMany - Condition:
{ account_id: ObjectId(accountId) } - Update:
{ deleted: new Date() } - TTL index auto-deletes after 500 seconds
- Collection:
Success Response:
{
"success": true,
"message": "SUCCESS"
}
Error Response:
{
"success": false,
"errno": 400,
"message": "Document Not Found!",
"additional_info": "Unable to find the correct document based on values provided!"
}
Error Handling:
- Returns 400 if account invalid
- Returns 400 if token not found
- Sets
statusCode: 400on errors
Side Effects:
- ⚠️ Deletes config from
analytics_tiktokanalytics_userconfigs - ⚠️ Soft deletes token (sets
deletedfield) - ⚠️ Sends FCM notifications to user and parent account
- ⚠️ Sends email notifications if enabled
- ⚠️ Token auto-deleted after 500 seconds (TTL index)
🔒 Token Storage Structure
MongoDB Document (tiktok.tokens):
{
"_id": ObjectId("..."),
"account_id": ObjectId("..."), // DashClicks account
"created_by": ObjectId("..."), // User who connected
"token": {
"code": 0,
"message": "OK",
"data": {
"access_token": "act.1234567890abcdef...",
"advertiser_ids": ["1234567890"],
"expires_in": 86400,
"token_type": "Bearer"
}
},
"deleted": Date, // Soft delete timestamp (optional)
"createdAt": ISODate("..."),
"updatedAt": ISODate("...")
}
Advertiser Config Document (analytics_tiktokanalytics_userconfigs):
{
"_id": ObjectId("..."),
"account_id": ObjectId("..."), // DashClicks account (unique)
"advertiser_id": "1234567890", // TikTok advertiser ID
"advertiser_name": "My Business Account",
"createdAt": ISODate("..."),
"updatedAt": ISODate("...")
}
🚨 Token Invalidation Detection
Automatic Detection (in index.js error middleware):
// Triggered on these conditions:
1. error.response.data.error == "invalid_grant"
2. error.response.data.error.code == "401" or "403"
3. error.response.data.error.status exists (missing permissions)
// Action taken:
await tikTokKey.updateMany(
{ account_id: ObjectId(accountId) },
{ $set: { token_invalidated: true } }
);
// User sees error:
{
"message": "TOKEN_INVALIDATED"
}
Manual Invalidation:
- User calls
DELETE /authendpoint - Token marked with
deleted: Date - Auto-deleted after 500 seconds (TTL index)
⚠️ Important Notes
- 🔐 JWT State Token: Contains account info, verified on callback
- 🔄 Force Re-auth: Login always deletes existing tokens
- 📦 Flexible Schema: Token storage uses
strict: falsefor TikTok response - ⏱️ Token Expiration: TikTok tokens expire in 86400 seconds (24 hours)
- ♻️ Soft Delete: Tokens marked
deleted, auto-removed after 500s - 🔒 Single Token: One token per account (upsert behavior)
- 🔔 Notifications: Respects user preferences for bell/email/browser
- 👥 Sub-accounts: Parent accounts notified of child account changes
- 🚨 Auto-Invalidation: API errors automatically mark tokens invalid
🔗 Related Documentation
- Integration Overview: TikTok Integration Index
- TikTok OAuth Docs: Marketing API Authorization
- Accounts: Advertiser Account Management
- Analytics: Campaign Reporting