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
┌──────────┐ ┌──────────────┐ ┌─────────────┐ ┌────────────┐
│ Your │ │ Euler Stream │ │ TikTok │ │ Euler Stream│
│ App │ │ Dashboard │ │ Mobile App │ │ API │
└────┬─────┘ └──────┬───────┘ └──────┬──────┘ └─────┬──────┘
│ │ │ │
│ 1. Redirect │ │ │
│─────────────────>│ │ │
│ │ │ │
│ │ 2. Show QR Code │ │
│ │<───────────────────│ │
│ │ │ │
│ │ 3. User Scans QR │ │
│ │<───────────────────│ │
│ │ │ │
│ │ 4. Generate Code │ │
│ │────────────────────────────────────-->│
│ │ │ │
│ 5. Redirect │ │ │
│ with code │ │ │
│<─────────────────│ │ │
│ │ │ │
│ 6. Exchange code for tokens │ │
│─────────────────────────────────────────────────────────>│
│ │ │ │
│ 7. Access + Refresh tokens │ │
│<─────────────────────────────────────────────────────────│Step 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: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) |
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
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) |
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:
interface TokenResponse {
access_token: string;
refresh_token: string;
token_type: 'Bearer';
expires_in: number; // seconds until access token expires (3600 = 1 hour)
scope: 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/x-www-form-urlencoded',
},
body: new URLSearchParams({
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!,
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error_description || 'Token exchange failed');
}
return response.json();
}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;
scope: string[];
}
function storeTokens(response: TokenResponse): UserTokens {
const expiresAt = new Date(Date.now() + response.expires_in * 1000);
return {
accessToken: response.access_token,
refreshToken: response.refresh_token,
expiresAt,
scope: response.scope.split(' '),
};
}Use the access token for API requests:
async function makeAuthenticatedRequest(
endpoint: string,
accessToken: string,
body?: object
) {
const response = await fetch(`https://tiktok.eulerstream.com${endpoint}`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${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: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/x-www-form-urlencoded' },
body: new URLSearchParams({
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 tokens = await tokenResponse.json();
if (!tokenResponse.ok) {
throw new Error(tokens.error_description || 'Token exchange failed');
}
// Store tokens in your database associated with the user
// await saveUserTokens(userId, tokens);
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