Skip to main content

🔐 Bing Ads - Authentication

📖 Overview

Microsoft Advertising API uses OAuth 2.0 Authorization Code Flow with automatic token refresh middleware. Access tokens expire after 1 hour (3600 seconds) and are automatically renewed using refresh tokens. The middleware intercepts every request to ensure valid tokens before SOAP API calls.

Source Files:

  • Controller: external/Integrations/BingAds/Controllers/Auth/AuthController.js
  • Model: external/Integrations/BingAds/Models/token.js
  • Middleware: external/Integrations/BingAds/Middleware/getToken.js

External API: Microsoft Identity Platform OAuth 2.0 endpoints

🗄️ Collections Used

bing.ads.tokens

  • Operations: Create, Read, Update
  • Model: shared/models/bing-ads-token.js
  • Usage Context: Store OAuth 2.0 tokens with automatic refresh capability

🔄 OAuth 2.0 Flow with Auto-Refresh

sequenceDiagram
participant User as User Browser
participant DC as DashClicks API
participant JWT as JWT Service
participant DB as MongoDB
participant MS as Microsoft OAuth
participant Middleware as Token Middleware

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 (10 min expiry)
JWT-->>DC: JWT with account/user info
DC->>User: Redirect to Microsoft OAuth
User->>MS: Authorize app
MS-->>User: Redirect with auth code
User->>DC: GET /callback?code=...&state=...
DC->>JWT: Verify state token
DC->>MS: POST /token (exchange code)
MS-->>DC: Access + refresh tokens
DC->>DB: Save tokens + generated_at
DC->>User: Redirect to forward_url (success)
end

Note over Middleware: Every API Request
User->>Middleware: API Request
Middleware->>DB: Fetch token document
DB-->>Middleware: Token + generated_at
alt Token Expired (> 3600s)
Middleware->>MS: POST /token (refresh_token grant)
MS-->>Middleware: New access token
Middleware->>DB: Update token + generated_at
end
Middleware->>DC: Continue with valid token

🔑 Environment Variables

VariableDescriptionExample
BING_ADS_CLIENT_IDMicrosoft Azure app client IDabc123...
BING_CLIENT_SECRETMicrosoft Azure app secretsecret123...
BING_ADS_REDIRECT_URLOAuth callback URL (must match Azure)https://api.dashclicks.com/v1/integrations/bing/ads/auth/callback
BING_ADS_CALLBACK_REDIRECT_URLFrontend redirect after OAuthhttps://app.dashclicks.com/integrations
BING_ADS_DEVELOPER_TOKENMicrosoft Ads developer tokenProduction token or BBD37VB98 (sandbox)
JWT_SECRETJWT signing secret for state tokenYour secure secret

🔧 Authentication Functions

Step 1: Initiate OAuth Flow

login()

Purpose: Start OAuth 2.0 authorization flow by redirecting to Microsoft authorization page

Source: Controllers/Auth/AuthController.js

Microsoft API Endpoint: https://login.microsoftonline.com/common/oauth2/v2.0/authorize

Request:

GET /v1/integrations/bing/ads/auth/login?forward_url=https://app.dashclicks.com/integrations
Authorization: Bearer {jwt_token}

Query Parameters:

ParameterTypeRequiredDescription
forward_urlStringURL to redirect after OAuth completion

Business Logic Flow:

  1. Validate Forward URL

    • Check if forward_url query parameter exists
    • Return error if missing: "forward url is missing.(required)"
  2. Check for Existing Token

    • Call TokenModel.findTokenID(uid, account_id)
    • If token exists, redirect immediately to success
    • Skip OAuth flow if already authenticated
  3. Generate JWT State Token

    • Sign JWT containing:
      • account_id - DashClicks account identifier
      • uid - User ID
      • forward_url - Redirect destination
    • Uses HS256 algorithm with JWT_SECRET
    • Expiration: 600 seconds (10 minutes)
  4. Build Microsoft Authorization URL

    • Base: https://login.microsoftonline.com/common/oauth2/v2.0/authorize
    • Tenant: common (multi-tenant)
    • Query parameters:
      • client_id - From BING_ADS_CLIENT_ID
      • response_type=code - OAuth code flow
      • redirect_uri - From BING_ADS_REDIRECT_URL
      • response_mode=query - Return code in query string
      • scope - OAuth scopes (space-separated)
      • state - Signed JWT token
  5. OAuth Scopes:

    • openid - OpenID Connect
    • offline_access - Refresh token grant
    • https://ads.microsoft.com/ads.manage - Microsoft Advertising API access
  6. Redirect User

    • HTTP 302 redirect to Microsoft authorization URL
    • User sees Microsoft consent screen

Microsoft Authorization URL Example:

https://login.microsoftonline.com/common/oauth2/v2.0/authorize
?client_id=abc123...
&response_type=code
&redirect_uri=https://api.dashclicks.com/v1/integrations/bing/ads/auth/callback
&response_mode=query
&scope=openid offline_access https://ads.microsoft.com/ads.manage
&state=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Success Response: 302 Redirect to Microsoft

Error Response:

// If forward_url missing
Error: 'forward url is missing.(required)';

// Passed to Express error handler

Side Effects:

  • ⚠️ Creates JWT state token with 10-minute expiration
  • ⚠️ Redirects user to external Microsoft authorization page

Step 2: OAuth Callback Handler

callback()

Purpose: Handle OAuth callback, exchange authorization code for tokens, and store in MongoDB

Source: Controllers/Auth/AuthController.js

Microsoft API Endpoint: POST https://login.microsoftonline.com/common/oauth2/v2.0/token

Callback Request:

GET /v1/integrations/bing/ads/auth/callback?code=...&state={jwt_token}
ParameterTypeDescription
codeStringOAuth authorization code
stateStringJWT state token from step 1

Business Logic Flow:

  1. Verify State Token

    • Verify JWT signature using JWT_SECRET
    • Check expiration (10 minutes)
    • Extract account ID, user ID, and forward URL
    • Pass error to middleware if verification fails
  2. Validate Authorization Code

    • Check if code query parameter exists
    • If missing, redirect with error: "auth redirect uri doesnt have auth code"
  3. Find or Create Token ID

    • Call TokenModel.findTokenID(uid, account_id)
    • Returns existing document ID or null
  4. Exchange Code for Tokens

    • HTTP Method: POST
    • URL: https://login.microsoftonline.com/common/oauth2/v2.0/token
    • Content-Type: application/x-www-form-urlencoded
    • Body Parameters:
      grant_type=authorization_code
      scope=openid offline_access https://ads.microsoft.com/ads.manage
      code={authorization_code}
      redirect_uri={BING_ADS_REDIRECT_URL}
      client_secret={BING_CLIENT_SECRET}
      client_id={BING_ADS_CLIENT_ID}
  5. Process Token Response

    • Extract access_token and refresh_token
    • Generate generated_at timestamp using moment().unix()
    • Store full OAuth response
  6. Save Token to MongoDB

    • Call TokenModel.setToken(tokenID, data)
    • Operation: findOneAndUpdate with upsert: true
    • Document Structure:
      {
      _id: ObjectId,
      uid: ObjectId(user_id),
      account_id: ObjectId(account_id),
      token: {
      access_token: "...",
      refresh_token: "...",
      expires_in: 3600,
      token_type: "Bearer",
      scope: "..."
      },
      generated_at: 1696942800, // Unix timestamp
      createdAt: Date,
      updatedAt: Date
      }
  7. Redirect to Success URL

    • Redirect to forward_url with:
      • status=success
      • integration=bing
      • token={mongodb_document_id}

Microsoft Token API Request:

POST https://login.microsoftonline.com/common/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
scope=openid+offline_access+https://ads.microsoft.com/ads.manage&
code=M.C123...&
redirect_uri=https://api.dashclicks.com/v1/integrations/bing/ads/auth/callback&
client_secret=your_secret&
client_id=your_client_id

Microsoft Token API Response:

{
"token_type": "Bearer",
"scope": "openid offline_access https://ads.microsoft.com/ads.manage",
"expires_in": 3600,
"access_token": "EwB4A8l6BAAU...",
"refresh_token": "M.C123..."
}

Success Response: 302 Redirect

{forward_url}?status=success&integration=bing&token={mongodb_document_id}

Error Response: 302 Redirect

{forward_url}?status=error&integration=bing&reason={error_message}

Error Handling:

  • JWT verification failure → Passed to Express error handler
  • Missing authorization code → Redirect with error message
  • Token exchange failure → Redirect with Microsoft error
  • Database save failure → Redirect with error message

Side Effects:

  • ⚠️ Creates or updates document in bing.ads.tokens collection
  • ⚠️ Stores generated_at Unix timestamp for expiration checking
  • ⚠️ Redirects user to success/error URL

Step 3: Automatic Token Refresh Middleware

getToken(req, res, next)

Purpose: Intercept every API request, check token expiration, and automatically refresh if needed

Source: Middleware/getToken.js

Applied To: All protected Bing Ads API routes (campaigns, ads, keywords, etc.)

Microsoft API Endpoint: POST https://login.microsoftonline.com/common/oauth2/v2.0/token

Business Logic Flow:

  1. Find Token Document

    • Extract uid and account_id from req.auth (JWT)
    • Call TokenModel.findTokenID(uid, account_id)
    • Get MongoDB document ID
  2. Fetch Token from Database

    • Call TokenModel.getToken(id)
    • Retrieve full token document
    • If not found, return error: "User oauth token not found. Please redirect to login"
  3. Check Token Expiration

    • Current time: moment().unix()
    • Token expiration: generated_at + 3600 (1 hour)
    • Formula: currentTime < (generated_at + 3600)
  4. If Token Valid (Not Expired):

    • Set token data on request object:
      req.oauth = doc;
      req.refresh_token = doc.token.refresh_token;
      req.access_token = doc.token.access_token;
      req.generated_at = doc.generated_at;
    • Call next() to continue to controller
  5. If Token Expired:

    • Build Refresh Request:
      • HTTP Method: POST
      • URL: https://login.microsoftonline.com/common/oauth2/v2.0/token
      • Content-Type: application/x-www-form-urlencoded
      • Body Parameters:
        grant_type=refresh_token
        scope=openid offline_access https://ads.microsoft.com/ads.manage
        refresh_token={existing_refresh_token}
        redirect_uri={BING_ADS_REDIRECT_URL}
        client_secret={BING_CLIENT_SECRET}
        client_id={BING_ADS_CLIENT_ID}
  6. Process Refresh Response:

    • Extract new access_token and refresh_token
    • Generate new generated_at timestamp
    • Set tokens on request object
  7. Update Database:

    • Call TokenModel.setToken(id, updatedData)
    • Update document with new tokens and generated_at
    • Operation: findOneAndUpdate
  8. Continue to Controller:

    • Call next() with updated tokens on request

Refresh Token Request Example:

POST https://login.microsoftonline.com/common/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&
scope=openid+offline_access+https://ads.microsoft.com/ads.manage&
refresh_token=M.C123...&
redirect_uri=https://api.dashclicks.com/v1/integrations/bing/ads/auth/callback&
client_secret=your_secret&
client_id=your_client_id

Refresh Token Response:

{
"token_type": "Bearer",
"scope": "openid offline_access https://ads.microsoft.com/ads.manage",
"expires_in": 3600,
"access_token": "EwB4A8l6BAAU...", // New access token
"refresh_token": "M.C456..." // New refresh token
}

Error Handling:

ErrorResponse
Token not foundError: "User oauth token not found. Please redirect to login"
Refresh token invalidMicrosoft error passed to Express error handler
Database update failureError passed to Express error handler

Side Effects:

  • ⚠️ Automatically refreshes expired tokens
  • ⚠️ Updates MongoDB document with new tokens and timestamp
  • ⚠️ Sets token data on req object for controllers
  • ⚠️ Blocks request if token refresh fails

Step 4: Delete Token

deleteToken(id)

Purpose: Delete OAuth token from MongoDB (disconnect integration)

Source: Models/token.js

Request:

DELETE /v1/integrations/bing/ads/auth/{token_id}
Authorization: Bearer {jwt_token}

Business Logic:

  • Calls BingAdsToken.deleteOne({ _id: id })
  • Removes document from bing.ads.tokens collection

Success Response:

{
"success": true,
"message": "Token deleted successfully"
}

Side Effects:

  • ⚠️ Permanently deletes token document
  • ⚠️ User must re-authenticate to use Bing Ads API

🔒 Token Storage Structure

MongoDB Document (bing.ads.tokens):

{
"_id": ObjectId("..."),
"account_id": ObjectId("..."), // DashClicks account
"uid": ObjectId("..."), // DashClicks user
"token": {
"token_type": "Bearer",
"scope": "openid offline_access https://ads.microsoft.com/ads.manage",
"expires_in": 3600,
"access_token": "EwB4A8l6BAAU...",
"refresh_token": "M.C123..."
},
"generated_at": 1696942800, // Unix timestamp
"createdAt": ISODate("..."),
"updatedAt": ISODate("...")
}

⏱️ Token Expiration Logic

Access Token Lifetime: 3600 seconds (1 hour)

Expiration Check:

const currentTime = moment().unix(); // Current Unix timestamp
const expirationTime = generated_at + 3600; // Token generated time + 1 hour
const isExpired = currentTime >= expirationTime;

Refresh Triggers:

  • Middleware checks expiration on every API request
  • If expired, automatically refreshes before processing request
  • No manual refresh needed by frontend

🔐 Security Features

JWT State Token:

  • Prevents CSRF attacks
  • 10-minute expiration prevents replay attacks
  • Contains account and user context

Refresh Token Rotation:

  • Microsoft returns new refresh token on each refresh
  • Old refresh token invalidated
  • Reduces risk of token theft

Offline Access:

  • offline_access scope enables long-lived refresh tokens
  • Users don't need to re-authenticate frequently

⚠️ Important Notes

  • 🔐 Azure App Registration: Must create app in Azure Portal
  • 🔄 Automatic Refresh: Middleware handles token refresh transparently
  • ⏱️ 1-Hour Expiration: Access tokens expire after 3600 seconds
  • ♻️ Refresh Token Rotation: New refresh token issued on each refresh
  • 🔒 Tenant: Uses common tenant for multi-tenant support
  • 📦 Developer Token: Required in addition to OAuth tokens
  • 🎯 Production Account: Sandbox limited to desktop apps
  • 💾 generated_at: Critical for expiration calculation
  • 🚫 No Manual Refresh: Frontend never needs to call refresh endpoint
  • 📝 Flexible Schema: strict: false allows full OAuth response storage
💬

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:30 AM