# OAuth 2.0

Allegro lets your service sign a member in through Allegro and receive that member's session token — the same RS256 JWT the browser SDK uses to act as the member. This is the flow to reach for when you run your own application (a mobile app, a companion site, a partner portal) and want "Sign in with Allegro" without building your own login for the tenant's audience.

Allegro implements the standard **OAuth 2.0 Authorization Code flow**, with PKCE supported and strongly recommended. PKCE is optional by default; a tenant can require it for your client (see [PKCE](#pkce)). The member authenticates on an Allegro-hosted page using whichever methods the tenant has enabled (magic link, one-time code, or social login), and your backend exchanges a short-lived code for the member's JWT.

The token never travels through the browser

Only a short-lived, single-use authorization code passes through the redirect URL, and that code is useless without your client secret (and your PKCE verifier when PKCE is in use). The JWT itself is only ever returned to your backend over a direct server-to-server request.

## Before you begin[​](#before-you-begin "Direct link to Before you begin")

A tenant administrator registers an OAuth client for you from the tenant's admin settings and gives you three things:

| Value           | Description                                                                                               |
| --------------- | --------------------------------------------------------------------------------------------------------- |
| `client_id`     | Public identifier for your integration. Safe to include in browser redirects.                             |
| `client_secret` | Secret shown **once** at creation. Store it securely on your backend; it is never exposed in the browser. |
| Redirect URI(s) | The exact URLs Allegro is allowed to send the member back to.                                             |

Redirect URIs are matched **exactly** — no wildcards, no trailing-slash flexibility, no query-string variance. Each URI you use must be registered with the client ahead of time and must be HTTPS (except `http://localhost` and `http://127.0.0.1` during local development).

A tenant administrator can edit a client after creation — updating its redirect URIs, renaming it, enabling or disabling it, and toggling whether PKCE is required — from the tenant's OAuth client settings. The client secret is not editable; it can only be regenerated (which invalidates the previous secret).

Everything below happens against the tenant's domain, for example `https://<your-tenant-domain>`. You can confirm the endpoints programmatically from the [discovery document](#discovery).

## The flow[​](#the-flow "Direct link to The flow")

```text
Your app / backend            Member's browser              Allegro (tenant domain)
     │                              │                               │
     │  1. redirect to authorize    │                               │
     │─────────────────────────────>│  GET /oauth/authorize         │
     │                              │──────────────────────────────>│  validate client_id
     │                              │  2. member signs in            │  + redirect_uri
     │                              │<───────── login page ──────────│
     │                              │                               │
     │                              │  3. redirect back with code    │
     │  redirect_uri?code=&state=   │<──────────────────────────────│
     │<─────────────────────────────│                               │
     │                              │                               │
     │  4. POST /oauth/token  (server-to-server, no browser)         │
     │───────────────────────────────────────────────────────────────>│  verify secret,
     │                                                               │  code + PKCE
     │  5. { access_token: <member JWT>, token_type, expires_in }    │
     │<───────────────────────────────────────────────────────────────│

```

## Step 1 — Redirect the member to the authorize endpoint[​](#step-1--redirect-the-member-to-the-authorize-endpoint "Direct link to Step 1 — Redirect the member to the authorize endpoint")

```text
GET https://<your-tenant-domain>/oauth/authorize

```

Generate a random `state` value and store it so you can verify it when the member returns. If you are using PKCE — recommended, and required when the tenant has enabled it for your client — generate the code verifier and challenge first; see [PKCE](#pkce).

Send the member's browser to the authorize endpoint with the following query parameters:

| Parameter               | Required   | Description                                                                               |
| ----------------------- | ---------- | ----------------------------------------------------------------------------------------- |
| `client_id`             | Yes        | Your client identifier.                                                                   |
| `redirect_uri`          | Yes        | One of your registered redirect URIs. Matched exactly.                                    |
| `response_type`         | Yes        | Must be `code`.                                                                           |
| `code_challenge`        | If enabled | The PKCE challenge (see [PKCE](#pkce)). Required unless PKCE is disabled for your client. |
| `code_challenge_method` | If enabled | Must be `S256` when `code_challenge` is present.                                          |
| `state`                 | No         | An opaque value round-tripped back to you unchanged. Use it for CSRF protection.          |
| `scope`                 | No         | Reserved for future use.                                                                  |

```js
const params = new URLSearchParams({
    client_id: CLIENT_ID,
    redirect_uri: 'https://your-app.example.com/callback',
    response_type: 'code',
    // Include code_challenge / code_challenge_method only when using PKCE.
    code_challenge: codeChallenge,
    code_challenge_method: 'S256',
    state,
});

const authorizeUrl = `https://<your-tenant-domain>/oauth/authorize?${params}`;
// Redirect the member's browser to authorizeUrl.

```

Allegro renders a sign-in page hosting the tenant's configured login methods. If the member already has a valid session on the tenant domain, they see a "Continue as {email}" prompt instead. Either way the member takes one explicit action, after which Allegro redirects the browser back to your `redirect_uri`.

Invalid requests never redirect

If the `client_id` is unknown or disabled, or the `redirect_uri` is not on the client's allowlist, Allegro renders an error page and **does not redirect**. This is deliberate — it prevents an attacker from using a malformed request to bounce a code to an unapproved destination. Double-check that the exact redirect URI you send is registered.

## Step 2 — Handle the redirect back[​](#step-2--handle-the-redirect-back "Direct link to Step 2 — Handle the redirect back")

Once the member authenticates, Allegro redirects the browser to:

```text
https://your-app.example.com/callback?code=<authorization-code>&state=<state>

```

Verify that `state` matches the value you generated in step 1, then take the `code`. The code is single-use and expires about **60 seconds** after it is issued, so exchange it immediately.

## Step 3 — Exchange the code for a token[​](#step-3--exchange-the-code-for-a-token "Direct link to Step 3 — Exchange the code for a token")

From your **backend** (never the browser — this request carries your client secret), POST to the token endpoint:

```text
POST https://<your-tenant-domain>/oauth/token
Content-Type: application/x-www-form-urlencoded

```

| Parameter       | Required   | Description                                                                                     |
| --------------- | ---------- | ----------------------------------------------------------------------------------------------- |
| `grant_type`    | Yes        | Must be `authorization_code`.                                                                   |
| `code`          | Yes        | The authorization code from step 2.                                                             |
| `redirect_uri`  | Yes        | The same redirect URI used in step 1. Matched exactly.                                          |
| `client_id`     | Yes        | Your client identifier.                                                                         |
| `client_secret` | Yes        | Your client secret.                                                                             |
| `code_verifier` | If enabled | The PKCE verifier (see [PKCE](#pkce)). Required whenever you sent a `code_challenge` in step 1. |

```bash
curl https://<your-tenant-domain>/oauth/token \
  -d grant_type=authorization_code \
  -d code="<authorization-code>" \
  -d redirect_uri="https://your-app.example.com/callback" \
  -d client_id="<your-client-id>" \
  -d client_secret="<your-client-secret>" \
  -d code_verifier="<your-code-verifier>"

```

On success you receive the member's JWT:

```json
{
    "access_token": "<member-jwt>",
    "token_type": "Bearer",
    "expires_in": 31536000
}

```

| Field          | Description                                         |
| -------------- | --------------------------------------------------- |
| `access_token` | The member's RS256 JWT. Use it as a `Bearer` token. |
| `token_type`   | Always `Bearer`.                                    |
| `expires_in`   | Token lifetime in seconds.                          |

The token represents a **fresh, independent session** minted for your integration. It appears among the member's sessions and can be revoked without affecting the member's own browser session. The token also carries an `azp` (authorized party) claim set to your `client_id`, so a partner-issued token is distinguishable from a browser SDK session token.

## Step 4 — Use the token[​](#step-4--use-the-token "Direct link to Step 4 — Use the token")

The `access_token` is a standard Allegro member JWT. Send it as a bearer token to the [Browser SDK API](/session-api), or verify it yourself against the tenant's public keys. See the [JWT Verification guide](/developer/guides/jwt.md) for the token structure, the JWKS endpoint, and verification examples.

## Discovery[​](#discovery "Direct link to Discovery")

Allegro advertises the OAuth and OIDC endpoints in its discovery document, so OAuth-aware libraries can configure themselves automatically:

```text
GET https://<your-tenant-domain>/.well-known/openid-configuration

```

The relevant fields are:

```json
{
    "issuer": "https://<your-tenant-domain>",
    "authorization_endpoint": "https://<your-tenant-domain>/oauth/authorize",
    "token_endpoint": "https://<your-tenant-domain>/oauth/token",
    "jwks_uri": "https://<your-tenant-domain>/.well-known/jwks.json",
    "response_types_supported": ["token", "code"],
    "grant_types_supported": ["authorization_code"],
    "code_challenge_methods_supported": ["S256"]
}

```

## Errors[​](#errors "Direct link to Errors")

The token endpoint returns a `400` response with an OAuth-style error code:

| Error            | Cause                                                                                                                                                     |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `invalid_client` | The `client_id` is unknown or disabled, or the `client_secret` is wrong.                                                                                  |
| `invalid_grant`  | The code is unknown, expired, or already used; the `redirect_uri` does not match the one bound to the code; or the `code_verifier` fails PKCE validation. |

```json
{ "error": "invalid_grant" }

```

Because codes are single-use and short-lived, a retried or double-submitted exchange returns `invalid_grant`. Start a new authorization request if that happens.

## PKCE[​](#pkce "Direct link to PKCE")

PKCE (`S256`) is **strongly recommended**. It is optional by default; a tenant administrator can require it for an individual client from the client's settings. Leaving it optional accommodates a server-side integration that authenticates with its client secret and cannot generate a verifier. Regardless of the requirement, if you send a `code_challenge` it is always enforced at the token endpoint (you cannot start a PKCE-protected flow and then drop the verifier), so we recommend using PKCE whenever your integration can.

When you use PKCE, generate a random **code verifier** and derive its **code challenge** before you redirect the member in [step 1](#step-1--redirect-the-member-to-the-authorize-endpoint).

* The verifier is a random string between 43 and 128 characters.
* The challenge is the base64url-encoded SHA-256 hash of the verifier, with padding removed. The `S256` method is required.

```js
import crypto from 'node:crypto';

const base64url = (buffer) =>
    buffer
        .toString('base64')
        .replace(/\+/g, '-')
        .replace(/\//g, '_')
        .replace(/=+$/, '');

const codeVerifier = base64url(crypto.randomBytes(32));
const codeChallenge = base64url(
    crypto.createHash('sha256').update(codeVerifier).digest(),
);

```

Send `code_challenge` (with `code_challenge_method=S256`) on the authorize request in [step 1](#step-1--redirect-the-member-to-the-authorize-endpoint), and keep the `code_verifier` on your backend (or in secure session storage) to send on the token exchange in [step 3](#step-3--exchange-the-code-for-a-token).

## Security notes[​](#security-notes "Direct link to Security notes")

* **PKCE (`S256`) is strongly recommended.** It is optional by default; a tenant can require it for your client. Whenever a `code_challenge` is sent, a matching `code_verifier` is required at the token endpoint.
* Authorization codes are single-use, expire in about 60 seconds, and are bound to the client, the exact `redirect_uri`, and the PKCE challenge.
* Redirect URIs use **exact-match** allowlisting — register every URI you intend to use.
* Always send and verify `state` to protect against CSRF.
* Keep the `client_secret` and `code_verifier` on your backend. The only value that legitimately passes through the browser is the authorization code.
