# 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()`:

```kotlin

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:

:::info[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)>`
  :::

- ![Node.js]

  ```javascript
  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 })
    }
  })
  ```

- ![Python]

  ```python
  import requests
  import base64
  import os
  from flask import Flask, request, jsonify

  app = Flask(__name__)

  @app.route('/api/auth/revolut/exchange', methods=['POST'])
  def exchange_code():
      data = request.json
      code = data.get('code')
      code_verifier = data.get('codeVerifier')

      try:
          # Create Basic Auth header with client credentials
          credentials = f"{os.environ['REVOLUT_CLIENT_ID']}:{os.environ['REVOLUT_CLIENT_SECRET']}"
          basic_auth = base64.b64encode(credentials.encode()).decode()

          response = requests.post(
              'https://sandbox-sso.revolut.com/api/token',
              data={
                  'grant_type': 'authorization_code',
                  'code': code,
                  'code_verifier': code_verifier,
              },
              headers={
                  'Content-Type': 'application/x-www-form-urlencoded',
                  'Authorization': f'Basic {basic_auth}',
              }
          )

          tokens = response.json()

          if 'id_token' in tokens:
              return jsonify({
                  'id_token': tokens['id_token']
              })
          else:
              return jsonify({'error': 'Token exchange failed'}), 400

      except Exception as e:
          return jsonify({'error': str(e)}), 500
  ```

- ![cURL]

  ```bash
  # Create Basic Auth credentials (base64 encode client_id:client_secret)
  AUTH=$(echo -n "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" | base64)

  curl -X POST https://sandbox-sso.revolut.com/api/token \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -H "Authorization: Basic $AUTH" \
    -d "grant_type=authorization_code" \
    -d "code=AUTHORIZATION_CODE" \
    -d "code_verifier=CODE_VERIFIER"
  ```

## Token response

Successful exchange returns ID token with user information:

```json
{
  "id_token": "..."
}
```

### Decoding the ID token

ID token is a JWT containing user information:

```json
{
  "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`:

```install
npm install jsonwebtoken
```

```javascript
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)
```

:::warning
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
  :::

:::tip [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](/docs/sdks/revolut-id-sdk/android/integration-guide#4-create-the-authenticator) for more details.
:::