OAuth Integration
Authorization Flow
A detailed walkthrough of the Euler Stream OAuth 2.0 authorization code flow with QR code authentication.
The Euler Stream OAuth system uses the standard OAuth 2.0 Authorization Code flow, enhanced with TikTok's QR code authentication for secure, password-less user verification.
Flow Overview
sequenceDiagram
participant App as Your App
participant Dash as Euler Stream Dashboard
participant TT as TikTok Mobile App
participant API as Euler Stream API
App->>Dash: 1. Redirect to /tiktok/oauth/authorize
Dash->>TT: 2. Display QR Code
TT->>Dash: 3. User scans QR & confirms
Dash->>API: 4. Generate authorization code
Dash->>App: 5. Redirect with code
App->>API: 6. Exchange code for tokens
API->>App: 7. Access + Refresh tokensStep 1: Initiate Authorization
Redirect users to the authorization endpoint with required parameters:
const authUrl = new URL('https://www.eulerstream.com/tiktok/oauth/authorize');
authUrl.searchParams.set('client_id', 'YOUR_CLIENT_ID');
authUrl.searchParams.set('redirect_uri', 'https://yourapp.com/callback');
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('scope', 'webcast:fetch webcast:bulk_live_check');
authUrl.searchParams.set('state', generateRandomState()); // CSRF protection
window.location.href = authUrl.toString();
Required Parameters
| Parameter | Description |
|---|---|
client_id | Your OAuth client ID from the dashboard |
redirect_uri | Must exactly match a registered redirect URI |
response_type | Must be code |
scope | Space-separated list of requested scopes |
Optional Parameters
| Parameter | Description |
|---|---|
state | Random string for CSRF protection (highly recommended) |
login_user_id | TikTok user ID to auto-select (skips account picker) |
Pinning a User with login_user_id
If your application already knows which TikTok user should authorize (e.g. from a previous session), you can pass login_user_id to skip the account picker and automatically recycle the user's existing session:
authUrl.searchParams.set('login_user_id', '6779789250017592326');
How it works:
- The authorize page checks if the user ID exists in the browser's cached sessions (localStorage + server-side session cookies)
- If found and the session is valid, authorization completes immediately — the user is redirected back with a code without any interaction
- If the user ID is not found or the session has expired, the parameter is silently ignored and the normal flow continues (account picker or QR code)
This is particularly useful for MCP integrations where a specific user context is needed. See the MCP OAuth Mode docs for details.
login_user_id only works when the user has previously completed the OAuth flow in the same browser. It cannot bypass authentication — it simply pre-selects an already-authenticated account.
Redirect URI Matching
Redirect URIs must exactly match a URI registered on your OAuth client, with one exception:
for http://localhost redirect URIs, the port is ignored per RFC 8252 §7.3.
This means you can register http://localhost/callback and use any port during development.
Step 2: User Authorization Flow
When users arrive at the authorization page, they go through three steps:
Introduction Screen
Users see your application's branding and a brief explanation of what's happening. This builds trust and transparency.
Permissions Review
Users review the exact permissions (scopes) your application is requesting. Each scope is displayed with a clear description of what it allows.
QR Code Authentication
Users scan a QR code with their TikTok mobile app to authenticate. This is the same secure method TikTok uses for web login:
- QR code is displayed and refreshes automatically if it expires
- User opens TikTok app and scans the code
- User confirms login on their phone
- Authorization completes automatically
If the user has previously authorized through this browser, they may see an account picker allowing them to select a previously-used TikTok account without scanning a new QR code.
Step 3: Handle the Callback
After successful authorization, users are redirected to your redirect_uri:
https://yourapp.com/callback?code=abc123xyz&state=YOUR_STATE_VALUE
Successful Response Parameters
| Parameter | Description |
|---|---|
code | Authorization code (valid for 10 minutes, single-use) |
state | The state value you provided (verify this!) |
Error Response Parameters
If authorization fails or the user cancels:
https://yourapp.com/callback?error=access_denied&error_description=User%20cancelled&state=YOUR_STATE_VALUE
| Parameter | Description |
|---|---|
error | Error code (see Error Codes) |
error_description | Human-readable error description |
state | The state value you provided |
Step 4: Exchange Code for Tokens
Exchange the authorization code for access and refresh tokens. This request must be made from your server (never client-side, as it requires your client secret).
interface TokenResponse {
access_token: string;
refresh_token: string;
token_type: 'Bearer';
expires_in: number; // seconds until access token expires (3600)
refresh_expires_in: number; // seconds until refresh token expires (2592000)
scopes: string[]; // granted scopes as an array
}
interface TokenEndpointResponse {
code: number;
message: string;
data?: TokenResponse;
error?: { error: string; error_description: string };
}
async function exchangeCodeForTokens(code: string): Promise<TokenResponse> {
const response = await fetch('https://tiktok.eulerstream.com/tiktok/oauth/token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
grant_type: 'authorization_code',
code: code,
redirect_uri: 'https://yourapp.com/callback',
client_id: process.env.OAUTH_CLIENT_ID!,
client_secret: process.env.OAUTH_CLIENT_SECRET!,
}),
});
const result: TokenEndpointResponse = await response.json();
if (result.code !== 200 || !result.data) {
throw new Error(result.error?.error_description || result.message || 'Token exchange failed');
}
return result.data;
}
Token Request Parameters
| Parameter | Description |
|---|---|
grant_type | Must be authorization_code |
code | The authorization code from the callback |
redirect_uri | Must match the original request |
client_id | Your OAuth client ID |
client_secret | Your OAuth client secret |
Step 5: Store and Use Tokens
After receiving tokens, store them securely:
interface UserTokens {
accessToken: string;
refreshToken: string;
expiresAt: Date;
refreshExpiresAt: Date;
scopes: string[];
}
function storeTokens(response: TokenResponse): UserTokens {
return {
accessToken: response.access_token,
refreshToken: response.refresh_token,
expiresAt: new Date(Date.now() + response.expires_in * 1000),
refreshExpiresAt: new Date(Date.now() + response.refresh_expires_in * 1000),
scopes: response.scopes,
};
}
Use the access token for API requests via the x-oauth-token header:
async function makeAuthenticatedRequest(
endpoint: string,
accessToken: string,
body?: object
) {
const response = await fetch(`https://tiktok.eulerstream.com${endpoint}`, {
method: 'POST',
headers: {
'x-oauth-token': accessToken,
'Content-Type': 'application/json',
},
body: body ? JSON.stringify(body) : undefined,
});
return response.json();
}
Complete TypeScript Example
Here's a complete Express.js implementation:
import express from 'express';
import crypto from 'crypto';
const app = express();
// Store state tokens (use Redis in production)
const stateStore = new Map<string, { createdAt: Date }>();
// Step 1: Initiate OAuth flow
app.get('/auth/tiktok', (req, res) => {
const state = crypto.randomBytes(32).toString('hex');
stateStore.set(state, { createdAt: new Date() });
const authUrl = new URL('https://www.eulerstream.com/tiktok/oauth/authorize');
authUrl.searchParams.set('client_id', process.env.OAUTH_CLIENT_ID!);
authUrl.searchParams.set('redirect_uri', `${process.env.APP_URL}/auth/callback`);
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('scope', 'webcast:fetch webcast:bulk_live_check');
authUrl.searchParams.set('state', state);
res.redirect(authUrl.toString());
});
// Step 3: Handle callback
app.get('/auth/callback', async (req, res) => {
const { code, state, error, error_description } = req.query;
// Handle errors
if (error) {
return res.status(400).json({ error, error_description });
}
// Verify state (CSRF protection)
if (!state || !stateStore.has(state as string)) {
return res.status(400).json({ error: 'Invalid state parameter' });
}
stateStore.delete(state as string);
// Step 4: Exchange code for tokens
try {
const tokenResponse = await fetch('https://tiktok.eulerstream.com/tiktok/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'authorization_code',
code: code as string,
redirect_uri: `${process.env.APP_URL}/auth/callback`,
client_id: process.env.OAUTH_CLIENT_ID!,
client_secret: process.env.OAUTH_CLIENT_SECRET!,
}),
});
const result = await tokenResponse.json();
if (result.code !== 200 || !result.data) {
throw new Error(result.error?.error_description || 'Token exchange failed');
}
// Store tokens in your database associated with the user
// await saveUserTokens(userId, result.data);
res.redirect('/dashboard?auth=success');
} catch (err) {
console.error('Token exchange error:', err);
res.redirect('/auth/error');
}
});
app.listen(3000);
Security Best Practices
- Always validate the
stateparameter to prevent CSRF attacks - Never expose your client secret in client-side code
- Use HTTPS for all redirect URIs (except localhost for development)
- Store tokens securely - encrypt at rest, never log them
- Implement token refresh before access tokens expire
- Handle token revocation gracefully when users disconnect
Next Steps
- Token Lifecycle - Learn about token expiration and refresh
- Error Codes - Handle all possible error scenarios