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

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:bulk_live_check');
authUrl.searchParams.set('state', generateRandomState()); // CSRF protection

window.location.href = authUrl.toString();

Required Parameters

ParameterDescription
client_idYour OAuth client ID from the dashboard
redirect_uriMust exactly match a registered redirect URI
response_typeMust be code
scopeSpace-separated list of requested scopes

Optional Parameters

ParameterDescription
stateRandom string for CSRF protection (highly recommended)
login_user_idTikTok 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:

  1. The authorize page checks if the user ID exists in the browser's cached sessions (localStorage + server-side session cookies)
  2. If found and the session is valid, authorization completes immediately — the user is redirected back with a code without any interaction
  3. 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:

  1. QR code is displayed and refreshes automatically if it expires
  2. User opens TikTok app and scans the code
  3. User confirms login on their phone
  4. 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

ParameterDescription
codeAuthorization code (valid for 10 minutes, single-use)
stateThe 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
ParameterDescription
errorError code (see Error Codes)
error_descriptionHuman-readable error description
stateThe 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

ParameterDescription
grant_typeMust be authorization_code
codeThe authorization code from the callback
redirect_uriMust match the original request
client_idYour OAuth client ID
client_secretYour 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

  1. Always validate the state parameter to prevent CSRF attacks
  2. Never expose your client secret in client-side code
  3. Use HTTPS for all redirect URIs (except localhost for development)
  4. Store tokens securely - encrypt at rest, never log them
  5. Implement token refresh before access tokens expire
  6. Handle token revocation gracefully when users disconnect

Next Steps