Skip to main content

🔐 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

VariableDescriptionExample
TIK_TOK_CLIENT_IDTikTok app ID from developer portalabc123...
TIKTOK_CLIENT_SECRETTikTok app secretsecret123...
TIK_TOK_REDIRECT_URLOAuth callback URL (must match portal)https://api.dashclicks.com/v1/integrations/tiktok/auth/callback
APP_SECRETJWT signing secret for state tokenYour secure secret
TIKTOK_BASE_URLTikTok Marketing API base URLhttps://business-api.tiktok.com
TIKTOK_VERSIONAPI versionv1.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}
ParameterTypeRequiredDescription
forward_urlStringURL to redirect after OAuth completion

Business Logic Flow:

  1. Validate Forward URL

    • Check if forward_url query parameter exists
    • Return 400 error if missing
  2. Check Account Access

    • Call checkAccountAccess(req) utility
    • Extract account ID and creator ID from JWT
    • Return 400 error if invalid account
  3. 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)
  4. Generate JWT State Token

    • Sign JWT containing:
      • account_id - DashClicks account identifier
      • forward_url - Redirect destination
      • created_by - User who initiated connection
    • Uses HS256 algorithm with APP_SECRET
    • No explicit expiration (short-lived flow)
  5. Build TikTok Authorization URL

    • Base: https://ads.tiktok.com/marketing_api/auth
    • Query parameters:
      • app_id - From TIK_TOK_CLIENT_ID env var
      • display=popup - UI display mode
      • state={jwt_token} - Signed state token
      • response_type=code - OAuth code flow
      • redirect_uri - From TIK_TOK_REDIRECT_URL env var
  6. 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_url with 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}
ParameterTypeDescription
codeStringOAuth authorization code
auth_codeStringTikTok authorization code (same as code)
stateStringJWT state token from step 1

Business Logic Flow:

  1. Verify State Token

    • Verify JWT signature using APP_SECRET
    • Extract account ID, forward URL, and creator ID
    • Return 403 error if invalid state
  2. Parse State Parameters

    • Decode JWT payload
    • Extract URLSearchParams from data field
    • Get account_id, forward_url, created_by
  3. Check for Existing Token

    • Call keysModel.find(accountId)
    • If token exists, redirect immediately to success
    • Continue if KEYS_NOT_FOUND error
  4. Validate Authorization Code

    • Check if code or auth_code query parameters exist
    • Return error redirect if missing
  5. 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}"
      }
  6. 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
  7. Redirect to Success URL

    • Redirect to forward_url with:
      • status=success
      • integration=tiktok
      • token={document_id} - MongoDB document ID

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.tokens collection
  • ⚠️ 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:

FieldTypeRequiredDescription
advertiser_idStringTikTok advertiser account ID
advertiser_nameStringAdvertiser account display name

Business Logic Flow:

  1. Validate Account Access

    • Get account ID via checkAccountAccess(req)
    • Return 400 if invalid
  2. Find Account Owner

    • Query User collection for is_owner: true user
    • Get owner's user ID
  3. Get Notification Preferences

    • Query user-config collection
    • Get preferences.projects.notifications.project_added settings
    • Check for bell/browser/email notification preferences
  4. Upsert Advertiser Configuration

    • Collection: analytics_tiktokanalytics_userconfigs
    • Operation: updateOne with upsert: true
    • Condition: { account_id: ObjectId(accountID) }
    • Data:
      {
      account_id: ObjectId(accountID),
      advertiser_id: "1234567890",
      advertiser_name: "My Business Account",
      updatedAt: Date
      }
  5. 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:

  1. Validate Account Access

    • Get account ID via checkAccountAccess(req)
    • Return 400 if invalid
  2. 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
  3. Find Existing Token

    • Call keysModel.find(accountId)
    • Return 400 if no token found
  4. Delete Advertiser Config

    • Find config in analytics_tiktokanalytics_userconfigs
    • If exists, delete via deleteOne({ _id: config._id })
  5. 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
      }]
      }
  6. Notify Parent Account (if sub-account)

    • If req.auth.parent_account != accountId
    • Send same notifications to parent account
    • Subject: "Tiktok integration for project disconnected"
  7. Soft Delete Token

    • Collection: tiktok.tokens
    • Operation: updateMany
    • Condition: { account_id: ObjectId(accountId) }
    • Update: { deleted: new Date() }
    • TTL index auto-deletes after 500 seconds

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: 400 on errors

Side Effects:

  • ⚠️ Deletes config from analytics_tiktokanalytics_userconfigs
  • ⚠️ Soft deletes token (sets deleted field)
  • ⚠️ 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 /auth endpoint
  • 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: false for 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
💬

Documentation Assistant

Ask me anything about the docs

Hi! I'm your documentation assistant. Ask me anything about the docs!

I can help you with:
- Code examples
- Configuration details
- Troubleshooting
- Best practices

Try asking: How do I configure the API?
09:31 AM