Sandbox
Help

Code exchange

After successful authentication, exchange the authorisation 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 the following values:

  • code — authorisation code from Revolut
  • codeVerifier — PKCE verification string

Receiving the code on Android

Handle the authorisation code in the RevolutIdHandleResult.Success branch of handle():

val uri = intent.data ?: return
when (val result = authenticator.handle(uri)) {
    is RevolutIdHandleResult.Success -> {
        // Send result.code and result.codeVerifier to your backend
        sendToBackend(code = result.code, codeVerifier = result.codeVerifier)
    }
    else -> { /* handle other cases */ }
}

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 RELEASE mode with your production client ID for live applications
    • exchange code endpoint: https://sso.revolut.com/api/token
  • Use SANDBOX mode with your sandbox client ID for development and testing
    • exchange code endpoint: https://sandbox-sso.revolut.com/api/token

See SDK configuration for more details.

Rate this page