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 RevolutcodeVerifier— 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:
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
| Claim | Description |
|---|---|
jti | JWT unique identifier |
iss | Token issuer (Revolut SSO) |
aud | Audience (your client ID) |
iat | Issued at timestamp |
exp | Expiration timestamp |
sub | Subject (unique user identifier) |
address | User's address (if requested in scope) |
phone | User's phone number (if requested in scope) |
phone_verified | Phone verification status |
email | User's email address (if requested in scope) |
email_verified | Email verification status |
Using ID tokens
Verify and decode ID token to access requested user data using jsonwebtoken:
npm install jsonwebtokenyarn add jsonwebtokenpnpm add jsonwebtokenbun add jsonwebtokenimport 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
The environment you use must match your client configuration:
- Use
RELEASEmode with your production client ID for live applications- exchange code endpoint:
https://sso.revolut.com/api/token
- exchange code endpoint:
- Use
SANDBOXmode with your sandbox client ID for development and testing- exchange code endpoint:
https://sandbox-sso.revolut.com/api/token
- exchange code endpoint:
See SDK configuration for more details.