> ## Documentation Index
> Fetch the complete documentation index at: https://docs.monei.cc/llms.txt
> Use this file to discover all available pages before exploring further.

# OAuth Flow

> A detailed walkthrough of every step in the Monei Connect Authorization Code Flow

This page explains each step of the Monei Connect OAuth 2.0 Authorization Code Flow in detail including edge cases, error handling, and things that trip developers up.

***

## Overview

```
Your App          User Browser           Monei
   │                    │                   │
   │── /connect-monei ──▶                   │
   │                    │── authorize ──────▶│
   │                    │    (login +        │
   │                    │   consent screen)  │
   │                    │◀─── redirect ──────│
   │◀── /callback?code ─│                   │
   │                    │                   │
   │────────────────────── POST /token ─────▶│
   │◀──────────────── access_token ─────────│
   │                    │                   │
   │────────────── GET /wallet/me ──────────▶│
   │◀─────────────── wallet data ───────────│
```

***

## Step 1: Build the authorization URL

Redirect your user to:

```
https://monei.cc/connect/authorize
  ?client_id=mc_a3f9b2c1d4e5
  &redirect_uri=https://yourapp.com/monei/callback
  &scope=wallet:read wallet:send profile:read
  &state=a3f9b2c1...
```

### Parameters

| Parameter      | Required             | Description                                                                                            |
| -------------- | -------------------- | ------------------------------------------------------------------------------------------------------ |
| `client_id`    | ✅                    | Your app's client ID                                                                                   |
| `redirect_uri` | ✅                    | Must exactly match a registered URI including protocol, path, no trailing slash differences            |
| `scope`        | ✅                    | Space-separated list of scopes you are requesting. See [Scopes](/connect/scopes)                       |
| `state`        | Strongly recommended | Random string you generate. Returned unchanged on callback. Validates the request wasn't tampered with |

### Generating state

Always generate `state` fresh per request and store it in the user's session:

<CodeGroup>
  ```javascript Node.js theme={null}
  import crypto from 'crypto';

  const state = crypto.randomBytes(16).toString('hex');
  req.session.oauthState = state;
  ```

  ```python Python theme={null}
  import secrets

  state = secrets.token_hex(16)
  session["oauth_state"] = state
  ```
</CodeGroup>

***

## Step 2: User approves on Monei

The user lands on Monei's consent screen. If not logged in to Monei, they are prompted to log in first.

The consent screen shows:

* Your app name and logo (from registration)
* Every scope you requested, in plain English
* A clear allow / deny choice per scope

**Users control individual scope approvals.** They can approve `wallet:read` but deny `wallet:send`. Your app must handle this. See [Partial Grants](/connect/partial-grants).

***

## Step 3: Monei redirects back

### On approval

```
https://yourapp.com/monei/callback?code=a8f3c2d9...&state=a3f9b2c1...
```

The `code` is short-lived, exchange it within **10 minutes** or it expires.

### On denial

```
https://yourapp.com/monei/callback?error=access_denied&state=a3f9b2c1...
```

Handle this gracefully and show the user what features won't be available and offer to try again.

### Validating state

**Always** compare the returned `state` to what you stored before proceeding:

<CodeGroup>
  ```javascript Node.js theme={null}
  const { code, state, error } = req.query;

  if (error) {
    return res.redirect('/dashboard?error=access_denied');
  }

  if (state !== req.session.oauthState) {
    // Possible CSRF attack — reject immediately
    return res.status(400).send('Invalid state');
  }

  // Safe to proceed
  ```

  ```python Python theme={null}
  code  = request.args.get("code")
  state = request.args.get("state")
  error = request.args.get("error")

  if error:
      return redirect("/dashboard?error=access_denied")

  if state != session.get("oauth_state"):
      # Possible CSRF attack — reject immediately
      return "Invalid state", 400

  # Safe to proceed
  ```
</CodeGroup>

***

## Step 4: Exchange the code for tokens

**This must happen server-side.** Never expose your `client_secret` in frontend or mobile code.

```bash theme={null}
POST /api/v1/connect/token
Content-Type: application/json

{
  "grant_type":    "authorization_code",
  "code":          "a8f3c2d9...",
  "client_id":     "mc_a3f9b2c1d4e5",
  "client_secret": "mcs_8d4e7f2a9b...",
  "redirect_uri":  "https://yourapp.com/monei/callback"
}
```

### Response

```json theme={null}
{
  "access_token":  "mct_...",
  "refresh_token": "mcr_...",
  "token_type":    "Bearer",
  "expires_in":    3600,
  "scopes":        ["wallet:read", "profile:read"]
}
```

<Warning>
  The `scopes` array reflects **what the user actually approved** — not everything you requested. A user could have declined some scopes. Always read this field and store it. See [Partial Grants](/connect/partial-grants).
</Warning>

### Implementation

<CodeGroup>
  ```javascript Node.js theme={null}
  async function exchangeCodeForTokens(code) {
    const res = await fetch('https://api.monei.cc/api/v1/connect/token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        grant_type:    'authorization_code',
        code,
        client_id:     process.env.MONEI_CLIENT_ID,
        client_secret: process.env.MONEI_CLIENT_SECRET,
        redirect_uri:  process.env.MONEI_REDIRECT_URI,
      }),
    });

    if (!res.ok) {
      const err = await res.json();
      throw new Error(`Token exchange failed: ${err.message}`);
    }

    return res.json();
  }
  ```

  ```python Python theme={null}
  import requests
  import os

  def exchange_code_for_tokens(code: str) -> dict:
      res = requests.post(
          "https://api.monei.cc/api/v1/connect/token",
          json={
              "grant_type":    "authorization_code",
              "code":          code,
              "client_id":     os.getenv("MONEI_CLIENT_ID"),
              "client_secret": os.getenv("MONEI_CLIENT_SECRET"),
              "redirect_uri":  os.getenv("MONEI_REDIRECT_URI"),
          },
      )

      if not res.ok:
          raise Exception(f"Token exchange failed: {res.json()}")

      return res.json()
  ```
</CodeGroup>

***

## Step 5: Call APIs on behalf of the user

Pass the access token as a Bearer token on every request:

<CodeGroup>
  ```javascript Node.js theme={null}
  const res = await fetch('https://api.monei.cc/api/v1/wallet/me', {
    headers: {
      Authorization: `Bearer ${accessToken}`,
    },
  });

  const wallet = await res.json();
  ```

  ```python Python theme={null}
  import requests

  res = requests.get(
      "https://api.monei.cc/api/v1/wallet/me",
      headers={"Authorization": f"Bearer {access_token}"},
  )

  wallet = res.json()
  ```
</CodeGroup>

The token only permits what the user approved. Calling an endpoint outside the granted scopes returns `403 Forbidden`.

***

## Common mistakes

**Using an expired code:** authorization codes expire in 10 minutes. Exchange them immediately in the callback handler.

**Skipping state validation:** always validate state. Skipping it leaves your users vulnerable to CSRF.

**Token exchange in the browser:** your `client_secret` must never touch the frontend. All token exchange happens on your server.

**Assuming all scopes were granted:** users can partially approve. Always check the `scopes` field in the token response.

**Not storing the refresh token:** access tokens expire in 1 hour. Store the refresh token so you can get a new one without the user re-authorizing.

***

<CardGroup cols={2}>
  <Card title="Token Management" icon="key" href="/connect/tokens">
    How to refresh, store, and revoke tokens
  </Card>

  <Card title="Partial Grants" icon="shield-halved" href="/connect/partial-grants">
    Handle users approving fewer scopes than requested
  </Card>
</CardGroup>
