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

# OAuth Integration Guide

> Integrate your application with FMG Connect using OAuth 2.1 (Authorization Code + PKCE).

FMG Connect uses **OAuth 2.1 Authorization Code flow with PKCE** to let your application access FMG resources on behalf of an FMG subscriber. This guide covers everything you need to integrate, assuming FMG has already provisioned your application.

## What FMG provides

During onboarding, FMG gives you:

| Item                | Description                                                                                                |
| ------------------- | ---------------------------------------------------------------------------------------------------------- |
| **Client ID**       | Public identifier for your application.                                                                    |
| **Client Secret**   | Confidential credential — used **server-side only** in the token exchange. Never expose it to the browser. |
| **Redirect URI(s)** | Your callback URL(s). Must match exactly at authorization time.                                            |
| **Scopes**          | The permissions provisioned for your app (see [Scopes](#scopes)).                                          |
| **Authorize URL**   | `https://secure.fmgsuite.com/login?returnUrl=/oauth/consent`                                               |
| **Token URL**       | `https://{stytch_domain}/v1/oauth2/token`                                                                  |
| **API Base URL**    | Base URL for FMG Connect API calls.                                                                        |

## Flow at a glance

<Steps>
  <Step title="Generate PKCE + state">
    Server-side, create a `code_verifier`, its `code_challenge` (S256), and a random `state`.
  </Step>

  <Step title="Redirect to authorize">
    Send the user's browser to the Authorize URL with your request parameters.
  </Step>

  <Step title="Handle the callback">
    FMG redirects back with `code` and `state`. Validate `state`.
  </Step>

  <Step title="Exchange code for tokens">
    Server-side `POST` to the Token URL with the `code`, `code_verifier`, and `client_secret`.
  </Step>

  <Step title="Call FMG APIs">
    Use the `access_token` as a Bearer token. Refresh it when it expires.
  </Step>
</Steps>

***

## Step 1 — Generate PKCE parameters

Generate these on your **server** and store `code_verifier` + `state` in the user's server-side session:

```text theme={null}
code_verifier  = base64url(random(32 bytes))   // 43-char string
code_challenge = base64url(SHA256(code_verifier))
state          = base64url(random(16 bytes))
```

## Step 2 — Redirect to authorization

Redirect the user's browser to the Authorize URL:

```text theme={null}
GET https://secure.fmgsuite.com/login?returnUrl=/oauth/consent
  &response_type=code
  &client_id={CLIENT_ID}
  &redirect_uri={REDIRECT_URI}
  &code_challenge={code_challenge}
  &code_challenge_method=S256
  &state={state}
  &scope={SCOPES}
```

<Warning>
  All OAuth parameters are appended as **top-level query parameters** alongside `returnUrl` — they are *not* encoded inside the `returnUrl` value:
  `https://secure.fmgsuite.com/login?returnUrl=/oauth/consent&response_type=code&client_id=...`
</Warning>

| Parameter               | Required | Value                                  |
| ----------------------- | -------- | -------------------------------------- |
| `response_type`         | Yes      | `code`                                 |
| `client_id`             | Yes      | Your Client ID                         |
| `redirect_uri`          | Yes      | Must exactly match a registered URI    |
| `code_challenge`        | Yes      | S256 challenge from Step 1             |
| `code_challenge_method` | Yes      | `S256`                                 |
| `state`                 | Yes      | Random CSRF value from Step 1          |
| `scope`                 | Yes      | Space-separated scopes assigned by FMG |

The user authenticates with FMG and approves a consent screen listing your requested permissions.

## Step 3 — Handle the callback

FMG redirects to your `redirect_uri`:

```text theme={null}
GET {REDIRECT_URI}?code={authorization_code}&state={state}
```

In your handler:

1. Compare `state` against the session value — abort on mismatch (CSRF).
2. Retrieve the `code_verifier` from the session.
3. Clear both from the session — they are single-use.

## Step 4 — Exchange the code for tokens

Server-side `POST` to the Token URL. Authenticate with your `client_secret`:

```bash theme={null}
curl -X POST https://{stytch_domain}/v1/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d grant_type=authorization_code \
  -d code={authorization_code} \
  -d client_id={CLIENT_ID} \
  -d client_secret={CLIENT_SECRET} \
  -d code_verifier={code_verifier} \
  -d redirect_uri={REDIRECT_URI}
```

**Response:**

```json theme={null}
{
  "access_token": "eyJhbGciOiJSUzI1NiIs...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "v2.abc...",
  "scope": "openid offline_access fmg.contacts.read fmg.contacts.write fmg.connect.health.read"
}
```

The `refresh_token` is only returned when the `offline_access` scope is granted. Store both tokens securely (server-side session or HttpOnly cookie — never `localStorage`).

## Step 5 — Call FMG Connect APIs

Send the access token as a Bearer token:

```bash theme={null}
curl https://{API_BASE_URL}/contacts \
  -H "Authorization: Bearer {access_token}"
```

## Step 6 — Refresh the access token

When the access token expires (`401` response), use the refresh token to get a new one without sending the user through the flow again:

```bash theme={null}
curl -X POST https://{stytch_domain}/v1/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d grant_type=refresh_token \
  -d refresh_token={refresh_token} \
  -d client_id={CLIENT_ID} \
  -d client_secret={CLIENT_SECRET}
```

***

## Scopes

Request exactly the scopes FMG assigned you — requesting an unprovisioned scope fails.

| Scope                     | Grants                                         |
| ------------------------- | ---------------------------------------------- |
| `openid`                  | Required — identity claims in the token.       |
| `offline_access`          | Returns a refresh token for long-lived access. |
| `fmg.content.read`        | Read the subscriber's marketing content.       |
| `fmg.contacts.read`       | Read the subscriber's contacts.                |
| `fmg.contacts.write`      | Create / update contacts.                      |
| `fmg.connect.health.read` | Read FMG Connect service health.               |

### Endpoints

| Method | Endpoint    | Required scope            |
| ------ | ----------- | ------------------------- |
| `GET`  | `/contacts` | `fmg.contacts.read`       |
| `POST` | `/contacts` | `fmg.contacts.write`      |
| `GET`  | `/health`   | `fmg.connect.health.read` |

***

## Errors

| Situation                | Result                                                 |
| ------------------------ | ------------------------------------------------------ |
| User denies consent      | Redirect to `redirect_uri` with `error=access_denied`. |
| `state` mismatch         | Reject the callback — possible CSRF.                   |
| Invalid / expired code   | Token exchange returns `400`.                          |
| `code_verifier` mismatch | Token exchange returns `400`.                          |
| Expired access token     | API returns `401` — refresh the token (Step 6).        |
| Expired refresh token    | Refresh returns `400` — restart from Step 2.           |
| FMG unavailable          | `503` — transient; retry after a short delay.          |

***

## Security requirements

* **PKCE (S256)** for every request; keep the `code_verifier` server-side.
* **Unique `state`** per request; validate on callback.
* **Token exchange and refresh are server-side only** — never expose `client_secret` or `code_verifier` to the browser.
* **HTTPS** for all redirect URIs and API calls.
* Store tokens in HttpOnly cookies or server-side sessions.
