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
- Generate PKCE — Create a
code_verifier(random string) and derivecode_challenge= BASE64URL(SHA256(code_verifier)). Also generate a randomstatefor CSRF protection. - Redirect to Deriv — Send the user to Deriv's authorization URL with all required parameters.
- User authenticates — Deriv shows either the login or registration form. All login and consent screens are managed by the OAuth provider.
- Redirect back — Deriv redirects the user to your
redirect_uriwith an authorizationcodeandstate. - Verify state — Confirm the returned
statematches what you stored. This prevents CSRF attacks. - Exchange code for token — Your backend sends the
code+code_verifierto Deriv's token endpoint and receives anaccess_token. - 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_idand a pre-registeredredirect_uri. - HTTPS enabled on your redirect URL.
- Your app must handle redirects, read the authorization code, and exchange it for tokens.
Step 1: Generate PKCE parameters
What is PKCE?
PKCE (Proof Key for Code Exchange, pronounced “pixy”) prevents authorization code interception attacks. Even if an attacker intercepts the authorization code, they cannot exchange it without the original code_verifier that only your app generated and stored.
| Term | What it is |
|---|---|
code_verifier | A cryptographically random string (43–128 characters) generated by your app |
code_challenge | BASE64URL(SHA256(code_verifier)) — sent with the authorization request |
code_challenge_method | Always S256 (SHA-256) |
Why it works: Only the app that generated the code_verifier can complete the token exchange. Even if an attacker intercepts the authorization code, they cannot exchange it without the verifier.
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);Storage tip
code_verifier and state in sessionStorage before redirecting — they survive the redirect and are automatically cleared when the tab is closed. Clear them from storage immediately after a successful token exchange.Step 2: Redirect the user to the authorization endpoint
Send users to Deriv's OAuth 2.0 authorization endpoint:
https://auth.deriv.com/oauth2/authLogin
Login uses the standard OAuth2 + PKCE parameters with no additions.
Parameters
| Parameter | Value | Description |
|---|---|---|
response_typeRequired | code | Request an authorization code |
client_idRequired | Your app ID | Registered OAuth2 application ID from Deriv |
redirect_uriRequired | Your callback URL | Must exactly match the URI registered with Deriv |
scopeRequired | trade | Requested permissions (trade or admin) |
stateRequired | Random string | CSRF protection — generate a new value for each request |
code_challengeRequired | BASE64URL(SHA256(verifier)) | The PKCE challenge derived from code_verifier |
code_challenge_methodRequired | S256 | Always SHA-256 |
app_idOptional | Your legacy app ID | Your V1 app ID from the Legacy Deriv API — include this only if you also maintain a legacy API app |
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
&state={RANDOM_STATE} # e.g. abc123random
&code_challenge={PKCE_CHALLENGE} # e.g. E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256Also maintaining a Legacy API app?
&app_id=YOUR_LEGACY_APP_ID to the login URL (and sign up URL). Deriv will check whether the user belongs to the old or new platform and route them to the appropriate version of your app.Login URL with legacy app support
https://auth.deriv.com/oauth2/auth?
response_type=code
&client_id={YOUR_CLIENT_ID}
&redirect_uri={YOUR_REDIRECT_URI}
&scope=trade
&state={RANDOM_STATE}
&code_challenge={PKCE_CHALLENGE}
&code_challenge_method=S256
&app_id={YOUR_LEGACY_APP_ID} # V1 app ID from legacy-api.deriv.comSign Up
Sign up uses the same base URL and parameters as login, plus one additional required parameter:
Required sign up parameter
| Parameter | Value | Description |
|---|---|---|
promptRequired | registration | Always 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 and set in the Partners dashboard. Include them to attribute signups to your partner account.
| Parameter | Value | Purpose |
|---|---|---|
sidc | Your session ID (GUID) | Tracking and attribution |
utm_campaign | Your campaign name | Identifies the marketing campaign |
utm_medium | affiliate | Indicates a partner integration |
utm_source | Your affiliate ID | Commission 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
&state={RANDOM_STATE} # e.g. abc123random
&code_challenge={PKCE_CHALLENGE} # e.g. E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256
&prompt=registration
&sidc={YOUR_SESSION_GUID} # e.g. 0FB46285-28A0-425E-B2E4-74F07D51EBB8
&utm_campaign={YOUR_CAMPAIGN} # e.g. dynamicworks
&utm_medium=affiliate
&utm_source={YOUR_AFFILIATE_ID} # e.g. CU303219Important
state parameter on return and generate your code_challenge from a secure random code_verifier. Never reuse these values between requests.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_STATEIf something went wrong:
https://yourapp.com/callback?error=access_denied&error_description=User+cancelledYour app must:
- Verify the state — compare the
statefrom the URL with the value you stored before the redirect. If they don't match, abort — it may be a CSRF attack. - Extract the code — read the
codequery parameter.
The authorization code is single-use and expires quickly
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/tokenRequest 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/callbackcURL 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": 3600,
"token_type": "Bearer"
}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_TOKENExample
curl -X GET "https://api.derivws.com/trading/v1/options/accounts" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"Quick reference
| Endpoint | URL |
|---|---|
| Authorization | https://auth.deriv.com/oauth2/auth |
| Token exchange | https://auth.deriv.com/oauth2/token |
| API base URL | https://api.derivws.com |
Where to find your values:
| Value | Where |
|---|---|
client_id | Register an OAuth2 app with Deriv — you'll receive an app ID |
redirect_uri | Set during app registration — must match exactly |
sidc (signup) | Managed and set in the Partners dashboard |
utm_source / affiliate ID (signup) | Managed and set in the Partners dashboard |
utm_campaign (signup) | Managed and set in the Partners dashboard |
app_id (legacy) | Your V1 app ID from legacy-api.deriv.com — only needed if you maintain a Legacy API app |
Troubleshooting
| Problem | Likely cause | Fix |
|---|---|---|
| State mismatch error | state in the callback doesn't match stored value | Store state in sessionStorage before redirecting, and don't regenerate it on page load |
invalid_grant on token exchange | code_verifier doesn't match the challenge, or code expired/already used | Send the original code_verifier, not a newly generated one; exchange the code immediately |
| Redirect URI mismatch | URL doesn't exactly match what's registered | Check for trailing slashes, http vs https, port numbers |
invalid_client | Wrong client_id | Verify your credentials from the Deriv dashboard |
| Login form shows instead of signup | Missing prompt=registration | Add prompt=registration to the authorization URL |
| Signup not tracked to partner | Missing or wrong UTM parameters | Verify sidc, utm_source, utm_medium, and utm_campaign are all present and correct |
Implementation checklist
Login
response_typeiscodeclient_idandredirect_uriare registered with Derivcode_challengeandstateare generated fresh for each requestcode_verifieris stored insessionStoragebefore redirect- Callback verifies
statebefore exchanging the code - Token exchange happens server-side (not in the browser)
code_verifieris cleared from storage after use- If maintaining a legacy app,
app_idis set to your Legacy app ID (optional)
Sign Up (additional)
promptis set toregistration(required)sidc,utm_source,utm_campaign, andutm_mediumare set if needed — get these from the Partners dashboard (optional)