Documentation
Getting Started

OAuth 2.0

A complete guide to implementing Login and Sign Up using Deriv's OAuth 2.0 Authorization Code flow with PKCE.

How the flow works

1
Generate PKCE
2
Redirect to Deriv
3
User Authenticates
4
Exchange Code
5
Use Token
  1. Generate PKCE — Create a code_verifier (random string) and derive code_challenge = BASE64URL(SHA256(code_verifier)). Also generate a random state for CSRF protection.
  2. Redirect to Deriv — Send the user to Deriv's authorization URL with all required parameters.
  3. User authenticates — Deriv shows either the login or registration form. All login and consent screens are managed by the OAuth provider.
  4. Redirect back — Deriv redirects the user to your redirect_uri with an authorization code and state.
  5. Verify state — Confirm the returned state matches what you stored. This prevents CSRF attacks.
  6. Exchange code for token — Your backend sends the code + code_verifier to Deriv's token endpoint and receives an access_token.
  7. Use the token — Make authenticated API calls using the Bearer token.

Before you start

You need:

  • A registered OAuth2 client from Deriv with a client_id and a pre-registered redirect_uri.
  • HTTPS enabled on your redirect URL. For local development, you can use http://localhost.
  • Your app must handle redirects, read the authorization code, and exchange it for tokens.

Step 1: Generate PKCE parameters

Generating PKCE in JavaScript

// 1. Generate a random code_verifier
const array = crypto.getRandomValues(new Uint8Array(64));
const codeVerifier = Array.from(array)
  .map(v => 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~'[v % 66])
  .join('');

// 2. Derive the code_challenge
const hash = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(codeVerifier));
const codeChallenge = btoa(String.fromCharCode(...new Uint8Array(hash)))
  .replace(/\+/g, '-')
  .replace(/\//g, '_')
  .replace(/=+$/, '');

// 3. Generate a random state for CSRF protection
const state = crypto.getRandomValues(new Uint8Array(16))
  .reduce((s, b) => s + b.toString(16).padStart(2, '0'), '');

// 4. Store code_verifier and state before redirecting
sessionStorage.setItem('pkce_code_verifier', codeVerifier);
sessionStorage.setItem('oauth_state', state);

Required authorization request parameters:

  • response_type=code
  • client_id
  • redirect_uri
  • scope
  • state
  • code_challenge + code_challenge_method=S256 (PKCE)

Step 2: Redirect the user to the authorization endpoint

Send users to Deriv's OAuth 2.0 authorization endpoint:

https://auth.deriv.com/oauth2/auth

Login

Login uses the standard OAuth2 + PKCE parameters with no additions.

Parameters

ParameterValueDescription
response_typeRequired
codeRequest an authorization code
client_idRequired
Your app IDRegistered OAuth2 application ID from Deriv
redirect_uriRequired
Your callback URLMust exactly match the URI registered with Deriv
scopeRequired
tradeaccount_manageapplication_readpayment
Space-separated list of the permissions your app requests — see the OAuth scopes table below
stateRequired
Random stringCSRF protection — generate a new value for each request
code_challengeRequired
BASE64URL(SHA256(verifier))The PKCE challenge derived from code_verifier
code_challenge_methodRequired
S256Always SHA-256

OAuth Scopes

The scope parameter is a space-separated list of the permissions your app requests. Request only the scopes your app needs.

ScopeDescription
tradeAccess to trading operations.
account_manageWrite access for account creation and management.
application_readRead-only access to your registered applications.
paymentAccess to payment agent deposit and withdrawal operations.

Login URL

https://auth.deriv.com/oauth2/auth?
  response_type=code
  &client_id={YOUR_CLIENT_ID}          # e.g. app12345
  &redirect_uri={YOUR_REDIRECT_URI}    # e.g. https://yourapp.com/callback
  &scope=trade+account_manage
  &state={RANDOM_STATE}                # e.g. abc123random
  &code_challenge={PKCE_CHALLENGE}     # e.g. E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256

Sign Up

Sign up uses the same base URL and parameters as login, plus one additional required parameter:

Required sign up parameter

ParameterValueDescription
promptRequired
registrationAlways this exact value. Tells Deriv to show the signup form instead of login.

Optional partner attribution parameters

The following parameters are all optional and managed in the Partners dashboard. Include them to attribute signups to your partner account. The tracking token parameter has four equivalent names (t, affiliate_token, sidi, ca) — use whichever one appears in your referral link or Partners dashboard.

ParameterValuePurpose
taffiliate_tokensidica
Your affiliate tracking tokenTracking and attribution. Use only one of these parameter names — they are equivalent aliases. Pick the one that appears in your referral link or in the Partners dashboard.
utm_campaignYour campaign nameIdentifies the marketing campaign
utm_mediumaffiliateIndicates a partner integration
utm_sourceYour affiliate IDCommission tracking and reporting

Sign Up URL

https://auth.deriv.com/oauth2/auth?
  response_type=code
  &client_id={YOUR_CLIENT_ID}          # e.g. app12345
  &redirect_uri={YOUR_REDIRECT_URI}    # e.g. https://yourapp.com/callback
  &scope=trade+account_manage
  &state={RANDOM_STATE}                # e.g. abc123random
  &code_challenge={PKCE_CHALLENGE}     # e.g. E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256
  &prompt=registration
  &t={YOUR_TRACKING_TOKEN}             # or: affiliate_token | sidi | ca — use the name from your referral link
  &utm_campaign={YOUR_CAMPAIGN}        # e.g. dynamicworks
  &utm_medium=affiliate
  &utm_source={YOUR_AFFILIATE_ID}      # e.g. CU303219

Step 3: Handle the callback

Whether the user logged in or signed up, the callback works exactly the same way. After authentication, Deriv redirects to your redirect_uri:

https://yourapp.com/callback?code=AUTHORIZATION_CODE&state=RANDOM_STATE

If something went wrong:

https://yourapp.com/callback?error=access_denied&error_description=User+cancelled

Your app must:

  1. Verify the state — compare the state from the URL with the value you stored before the redirect. If they don't match, abort — it may be a CSRF attack.
  2. Extract the code — read the code query parameter.

Step 4: Exchange code for tokens

Make a POST request from your backend to the token endpoint. Never perform the token exchange from the browser.

POST https://auth.deriv.com/oauth2/token

Request body (form-encoded)

grant_type=authorization_code
client_id=YOUR_CLIENT_ID
code=AUTH_CODE_FROM_CALLBACK
code_verifier=YOUR_ORIGINAL_CODE_VERIFIER
redirect_uri=https://your-app.com/callback

cURL example

curl -X POST https://auth.deriv.com/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "code=AUTH_CODE" \
  -d "code_verifier=YOUR_CODE_VERIFIER" \
  -d "redirect_uri=https://your-app.com/callback"

Token response

{
  "access_token": "ory_at_...",
  "expires_in": 2592000,
  "token_type": "Bearer"
}

expires_in is the token's lifetime in seconds. The response doesn't include a refresh token — see Token lifetime and renewal.

Step 5: Use the access token in API calls

Include the access token as a Bearer token in the Authorization header for all API calls:

Authorization: Bearer YOUR_ACCESS_TOKEN

Example

curl -X GET "https://api.derivws.com/trading/v1/options/accounts" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Token lifetime and renewal

Every access token expires. Read its lifetime in seconds from expires_in in the token response, then store the expiry time (the time you received the token plus expires_in) with the token. Don't hardcode a lifetime — it can change.

To get a new access token, send the user through the authorization flow (Authorization Code + PKCE) again from step 1 when:

  • The number of seconds in expires_in has passed
  • An API call returns 401

If a new access token also gets a 401, don't start the flow again. Check the Authorization header and the endpoint instead.

Quick reference

EndpointURL
Authorizationhttps://auth.deriv.com/oauth2/auth
Token exchangehttps://auth.deriv.com/oauth2/token
API base URLhttps://api.derivws.com

Where to find your values:

ValueWhere
client_idRegister an OAuth2 app with Deriv — you'll receive an app ID
redirect_uriSet during app registration — must match exactly
t / affiliate_token / sidi / ca (signup)Your referral link or the Partners dashboard — use the exact parameter name shown there
utm_source / affiliate ID (signup)Managed and set in the Partners dashboard
utm_campaign (signup)Managed and set in the Partners dashboard

Troubleshooting

ProblemLikely causeFix
State mismatch errorstate in the callback doesn't match stored valueStore state in sessionStorage before redirecting, and don't regenerate it on page load
invalid_grant on token exchangecode_verifier doesn't match the challenge, or code expired/already usedSend the original code_verifier, not a newly generated one; exchange the code immediately
Redirect URI mismatchURL doesn't exactly match what's registeredCheck for trailing slashes, http vs https, port numbers
invalid_clientWrong client_idVerify your credentials from the Deriv dashboard
Login form shows instead of signupMissing prompt=registrationAdd prompt=registration to the authorization URL
Signup not tracked to partnerMissing or wrong UTM parametersVerify your tracking token parameter (one of t, affiliate_token, sidi, or ca) matches the one shown in your referral link, and that utm_source, utm_medium, and utm_campaign are all present and correct
401 on calls that used to workThe access token expiredRun the authorization flow again from step 1 for a new access token — there's no refresh token

Implementation checklist

Login

  • response_type is code
  • client_id and redirect_uri are registered with Deriv
  • code_challenge and state are generated fresh for each request
  • code_verifier is stored in sessionStorage before redirect
  • Callback verifies state before exchanging the code
  • Token exchange happens server-side (not in the browser)
  • code_verifier is cleared from storage after use
  • The authorization flow runs again when the token expires or a call returns 401, but not when a new access token also gets a 401

Sign Up (additional)

  • prompt is set to registration (required)
  • Tracking token (one of t, affiliate_token, sidi, ca) utm_source, utm_campaign, and utm_medium are set if needed — use the parameter name shown in your referral link or Partners dashboard (optional)
Click to open live chat support. Get instant help from our support team.