Sandbox
Help

Code exchange

After successful authentication, exchange the authorization code for access tokens to retrieve user data. The code exchange process happens on your backend server using OAuth 2.0 with PKCE.

How it works

The authentication flow provides you with two essential values:

  • code - authorization code from Revolut
  • codeVerifier - PKCE verification string

You'll receive these values differently depending on your chosen display mode:

When using popup mode, handle the authorization code in the success callback:

const { mountButton } = revolutId.initialise({
  clientId: 'your-client-id',
  redirectUri: 'https://your-app.com/callback',
  scope: ['openid', 'profile', 'email'],
  displayMode: 'popup',
  onAuthSuccess: async ({ code, codeVerifier }) => {
    // Send to your backend for token exchange
    try {
      const response = await fetch('/api/auth/revolut/exchange', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ code, codeVerifier }),
      })

      const tokens = await response.json()
      console.log('Access token received:', tokens.access_token)

      // Use tokens to fetch user data or create session
    } catch (error) {
      console.error('Token exchange failed:', error)
    }
  },
})

// Mount button
const buttonClient = mountButton(document.getElementById('auth-button'))

Redirect display mode - processRedirectParams

When using redirect mode, process the authorization result on your redirect URI page:

// On your redirect URI page (https://your-app.com/callback)
const result = revolutId.processRedirectParams()

if (result?.status === 'success') {
  const { code, codeVerifier } = result

  // Send to your backend for token exchange
  fetch('/api/auth/revolut/exchange', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ code, codeVerifier }),
  })
    .then((response) => response.json())
    .then((tokens) => {
      console.log('Access token received:', tokens.access_token)
      // Redirect to authenticated area or update UI
      window.location.href = '/dashboard'
    })
    .catch((error) => {
      console.error('Token exchange failed:', error)
    })
} else if (result?.status === 'error') {
  console.error('Authentication failed:', result.error)
}

Backend: Token exchange

Exchange authorization code for access tokens using HTTP Basic Authentication with your client credentials:

Authentication Required

All token exchange requests must include Basic Authentication using your client ID and client secret:

  • Username: Your client ID
  • Password: Your client secret
  • Header: Authorization: Basic <base64(clientId:clientSecret)>
app.post('/api/auth/revolut/exchange', async (req, res) => {
  const { code, codeVerifier } = req.body

  try {
    // Create Basic Auth header with client credentials
    const basicAuth = Buffer.from(
      `${process.env.REVOLUT_CLIENT_ID}:${process.env.REVOLUT_CLIENT_SECRET}`,
    ).toString('base64')

    const response = await fetch(
      'https://sandbox-sso.revolut.com/api/token',
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
          Authorization: `Basic ${basicAuth}`,
        },
        body: new URLSearchParams({
          grant_type: 'authorization_code',
          code: code,
          code_verifier: codeVerifier,
        }),
      },
    )

    const tokens = await response.json()

    if (tokens.id_token) {
      res.json({
        id_token: tokens.id_token,
      })
    } else {
      res.status(400).json({ error: 'Token exchange failed' })
    }
  } catch (error) {
    res.status(500).json({ error: error.message })
  }
})

Token response

Successful exchange returns ID token with user information:

{
  "id_token": "..."
}

Decoding the ID token

ID token is a JWT containing user information:

{
  "jti": "...",
  "iss": "https://sso.revolut.com",
  "aud": "your-client-id",
  "iat": 1747670512, // issued at in ms
  "exp": 1747670532, // expired at in ms
  "amr": [],
  "sub": "...",
  "amrv": [],
  "address": "...",
  "phone": "+0000000000",
  "phone_verified": "true",
  "email": "user@example.com",
  "email_verified": "false"
}

ID token claims

ClaimDescription
jtiJWT unique identifier
issToken issuer (Revolut SSO)
audAudience (your client ID)
iatIssued at timestamp
expExpiration timestamp
subSubject (unique user identifier)
addressUser's address (if requested in scope)
phoneUser's phone number (if requested in scope)
phone_verifiedPhone verification status
emailUser's email address (if requested in scope)
email_verifiedEmail verification status

Using ID tokens

Verify and decode ID token to access requested user data using jsonwebtoken:

npm install jsonwebtoken
yarn add jsonwebtoken
pnpm add jsonwebtoken
bun add jsonwebtoken
import jwt from 'jsonwebtoken'
import crypto from 'crypto'

async function getPublicKey(kid) {
  try {
    const response = await fetch(
      'https://sso.revolut.com/.well-known/jwks.json',
    )
    const jwks = await response.json()

    const key = jwks.keys.find((k) => k.kid === kid)
    if (!key) {
      throw new Error(`Key with kid ${kid} not found`)
    }

    const publicPemKey = crypto.createPublicKey({
      key: key,
      format: 'jwk',
    })

    return publicPemKey.export({ type: 'spki', format: 'pem' })
  } catch (error) {
    throw new Error(`Failed to fetch public key: ${error.message}`)
  }
}

async function verifyIdToken(id_token) {
  try {
    const header = jwt.decode(id_token, { complete: true })?.header
    if (!header?.kid) {
      throw new Error('Invalid token: missing key ID')
    }

    const publicKey = await getPublicKey(header.kid)

    const decoded = jwt.verify(id_token, publicKey, {
      issuer: 'https://sso.revolut.com',
      audience: 'your-client-id',
      algorithms: ['RS256'],
    })

    return decoded
  } catch (error) {
    console.error('Token verification failed:', error.message)
    throw error
  }
}

const decoded = verifyIdToken(id_token)

console.log('User ID:', decoded.sub)
console.log('Email:', decoded.email)
console.log('Phone:', decoded.phone)
console.log('Address:', decoded.address)

Security best practices:

  • Never expose client secrets in frontend code
  • Always perform token exchange on your secure backend server
  • Store tokens securely and implement proper session management
  • Use HTTPS for all authentication-related requests
  • Validate and verify ID tokens before trusting user information
Environment setup

The environment you use must match your client configuration:

  • Use production mode with your production client ID for live applications

    • exchange code endpoint: https://sso.revolut.com/token
  • Use sandbox mode with your sandbox client ID for development and testing

    • exchange code endpoint: https://sandbox-sso.revolut.com/token

See environment configuration for more details.

Rate this page