🔐 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
| Variable | Description | Example |
|---|---|---|
BING_ADS_CLIENT_ID | Microsoft Azure app client ID | abc123... |
BING_CLIENT_SECRET | Microsoft Azure app secret | secret123... |
BING_ADS_REDIRECT_URL | OAuth callback URL (must match Azure) | https://api.dashclicks.com/v1/integrations/bing/ads/auth/callback |
BING_ADS_CALLBACK_REDIRECT_URL | Frontend redirect after OAuth | https://app.dashclicks.com/integrations |
BING_ADS_DEVELOPER_TOKEN | Microsoft Ads developer token | Production token or BBD37VB98 (sandbox) |
JWT_SECRET | JWT signing secret for state token | Your 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:
| 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 error if missing: "forward url is missing.(required)"
- Check if
-
Check for Existing Token
- Call
TokenModel.findTokenID(uid, account_id) - If token exists, redirect immediately to success
- Skip OAuth flow if already authenticated
- Call
-
Generate JWT State Token
- Sign JWT containing:
account_id- DashClicks account identifieruid- User IDforward_url- Redirect destination
- Uses HS256 algorithm with
JWT_SECRET - Expiration: 600 seconds (10 minutes)
- Sign JWT containing:
-
Build Microsoft Authorization URL
- Base:
https://login.microsoftonline.com/common/oauth2/v2.0/authorize - Tenant:
common(multi-tenant) - Query parameters:
client_id- FromBING_ADS_CLIENT_IDresponse_type=code- OAuth code flowredirect_uri- FromBING_ADS_REDIRECT_URLresponse_mode=query- Return code in query stringscope- OAuth scopes (space-separated)state- Signed JWT token
- Base:
-
OAuth Scopes:
openid- OpenID Connectoffline_access- Refresh token granthttps://ads.microsoft.com/ads.manage- Microsoft Advertising API access
-
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}
| Parameter | Type | Description |
|---|---|---|
code | String | OAuth authorization code |
state | String | JWT state token from step 1 |
Business Logic Flow:
-
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
- Verify JWT signature using
-
Validate Authorization Code
- Check if
codequery parameter exists - If missing, redirect with error: "auth redirect uri doesnt have auth code"
- Check if
-
Find or Create Token ID
- Call
TokenModel.findTokenID(uid, account_id) - Returns existing document ID or null
- Call
-
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}
-
Process Token Response
- Extract
access_tokenandrefresh_token - Generate
generated_attimestamp usingmoment().unix() - Store full OAuth response
- Extract
-
Save Token to MongoDB
- Call
TokenModel.setToken(tokenID, data) - Operation:
findOneAndUpdatewithupsert: 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
}
- Call
-
Redirect to Success URL
- Redirect to
forward_urlwith:status=successintegration=bingtoken={mongodb_document_id}
- Redirect to
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.tokenscollection - ⚠️ Stores
generated_atUnix 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:
-
Find Token Document
- Extract
uidandaccount_idfromreq.auth(JWT) - Call
TokenModel.findTokenID(uid, account_id) - Get MongoDB document ID
- Extract
-
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"
- Call
-
Check Token Expiration
- Current time:
moment().unix() - Token expiration:
generated_at + 3600(1 hour) - Formula:
currentTime < (generated_at + 3600)
- Current time:
-
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
- Set token data on request object:
-
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}
- Build Refresh Request:
-
Process Refresh Response:
- Extract new
access_tokenandrefresh_token - Generate new
generated_attimestamp - Set tokens on request object
- Extract new
-
Update Database:
- Call
TokenModel.setToken(id, updatedData) - Update document with new tokens and
generated_at - Operation:
findOneAndUpdate
- Call
-
Continue to Controller:
- Call
next()with updated tokens on request
- Call
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:
| Error | Response |
|---|---|
| Token not found | Error: "User oauth token not found. Please redirect to login" |
| Refresh token invalid | Microsoft error passed to Express error handler |
| Database update failure | Error passed to Express error handler |
Side Effects:
- ⚠️ Automatically refreshes expired tokens
- ⚠️ Updates MongoDB document with new tokens and timestamp
- ⚠️ Sets token data on
reqobject 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.tokenscollection
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_accessscope 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
commontenant 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: falseallows full OAuth response storage
🔗 Related Documentation
- Integration Overview: Bing Ads Integration Index
- Microsoft OAuth Docs: OAuth 2.0 Authorization Code Flow
- Azure Portal: App Registration
- Developer Token: Get Microsoft Ads Developer Token
- Accounts: Manager and User Accounts
- Campaigns: Campaign Management