---
title: OIDC flow
description: Integrate an OpenID Connect relying party with discovery,
  authorization code + PKCE, /token, /userinfo, and /end-session.
editUrl: true
head: []
template: doc
sidebar:
  order: 1
  hidden: false
  attrs: {}
pagefind: true
draft: false
---

import { Tabs, TabItem } from "@astrojs/starlight/components";

If you have an existing application that already speaks **OpenID Connect**, or you want to use an off-the-shelf OIDC library, you can integrate with Sudomimus as a standard OIDC relying party (RP). The Sudomimus OIDC provider lives at `oidc.sudomimus.com` and supports the **authorization code flow with PKCE**, the canonical modern OIDC integration shape.

Use this guide when:

- Your framework or platform has a first-class OIDC integration (Next-Auth, Spring Security, Keycloak adapter, etc.) and you want to slot Sudomimus in as the IdP.
- You're integrating with a partner system that already expects an OIDC provider.
- You prefer the OIDC mental model (clients, scopes, ID tokens) to the Connect protocol.

If you're starting fresh and just want the smallest custom integration, the [Connect protocol](/en-us/connect/flow/) is usually shorter.

## Discovery

Sudomimus publishes a standard OIDC discovery document. Point your library at the issuer URL `https://oidc.sudomimus.com` and it will fetch the rest from there:

```bash
curl https://oidc.sudomimus.com/.well-known/openid-configuration
```

The document advertises:

- **`response_types_supported`**: `["code"]` (authorization code flow only).
- **`grant_types_supported`**: `["authorization_code", "refresh_token"]`.
- **`scopes_supported`**: `["openid", "email", "profile", "offline_access"]`.
- **`id_token_signing_alg_values_supported`**: `["RS256"]`.
- **`code_challenge_methods_supported`**: `["S256"]` — **PKCE is required**, plain not supported.
- **`token_endpoint_auth_methods_supported`**: `["private_key_jwt", "client_secret_basic", "client_secret_post", "none"]`.

## Register your application

In [`with.sudomimus.com`](https://with.sudomimus.com), on the application you want to expose via OIDC:

1. Add a Layer 3 **OIDC** return rule:

   ```json
   {
     "returnMethod": "OIDC",
     "payload": {
       "redirectUris": ["https://app.example.com/oidc/callback"],
       "postLogoutRedirectUris": ["https://app.example.com/"],
       "allowedScopes": ["openid", "email", "profile", "offline_access"],
       "tokenEndpointAuthMethod": "private_key_jwt"
     }
   }
   ```

2. Add the Layer 1 and Layer 2 rules you would for any other application — at least one authentication method (e.g. `PASSKEY_USERNAMELESS` or `PASSKEY_REASONED`) and at least one realize rule (e.g. `EMAIL` with the addresses or domain pattern you accept). The OIDC flow runs through the same authentication challenge as the rest of the platform.

3. **Choose a client authentication method**:
   - **`private_key_jwt`** (recommended for confidential clients) — your RP holds a private key and signs a JWT assertion at `/token`. The signing key is your application's client-auth key (the same key used to sign `/establish` requests on the native protocol).
   - **`client_secret_basic`** (confidential clients) — your RP presents its shared secret in the HTTP `Authorization: Basic` header at `/token`.
   - **`client_secret_post`** (confidential clients) — your RP sends its shared secret in the `/token` form body (`client_id` + `client_secret` parameters).
   - **`none`** — only for public clients (SPAs, mobile apps without a backend). **PKCE is required.**

The application's **`applicationAnchor`** is your `client_id`.

## The OIDC flow

### 1. Authorization request

Redirect the user's browser to `/authorize` with the standard OIDC parameters:

```text
https://oidc.sudomimus.com/authorize
  ?client_id=my-app
  &redirect_uri=https%3A%2F%2Fapp.example.com%2Foidc%2Fcallback
  &response_type=code
  &scope=openid%20email%20profile
  &state=<csrf-token>
  &nonce=<random-nonce>
  &code_challenge=<S256-of-verifier>
  &code_challenge_method=S256
```

Required: `client_id`, `redirect_uri`, `response_type=code`, `scope` (must include `openid`), `code_challenge`, `code_challenge_method=S256`.
Optional but recommended: `state`, `nonce`.

Sudomimus redirects the user to `via.sudomimus.com` where they authenticate via the methods allowed by your Layer 1 rules. After a successful authentication and realize, the browser returns to your `redirect_uri` with a `code` and the original `state`.

### 2. Token exchange

POST the authorization code to `/token`. The body is **`application/x-www-form-urlencoded`**, per OIDC:

<Tabs syncKey="oidc-client">
<TabItem label="private_key_jwt">
```bash
curl -X POST https://oidc.sudomimus.com/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=authorization_code" \
  --data-urlencode "code=$AUTH_CODE" \
  --data-urlencode "redirect_uri=https://app.example.com/oidc/callback" \
  --data-urlencode "code_verifier=$PKCE_VERIFIER" \
  --data-urlencode "client_id=my-app" \
  --data-urlencode "client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
  --data-urlencode "client_assertion=$CLIENT_ASSERTION_JWT"
```

The `client_assertion` is a JWT you sign with your application's client-auth private key. Required claims: `iss = client_id`, `sub = client_id`, `aud = the exact token endpoint URL` (`https://oidc.sudomimus.com/token` in production), fresh `jti`, `iat`, `exp` (within 300s of `iat`). RS256.
</TabItem>
<TabItem label="none (PKCE)">
```bash
curl -X POST https://oidc.sudomimus.com/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=authorization_code" \
  --data-urlencode "code=$AUTH_CODE" \
  --data-urlencode "redirect_uri=https://app.example.com/oidc/callback" \
  --data-urlencode "code_verifier=$PKCE_VERIFIER" \
  --data-urlencode "client_id=my-app"
```

No client assertion is sent. PKCE (`code_verifier` matching the `code_challenge` from the authorization request) is the only client authentication.
</TabItem>
</Tabs>

Successful response (JSON):

```json
{
  "access_token": "<JWT>",
  "token_type": "Bearer",
  "expires_in": 10800,
  "id_token": "<JWT>",
  "scope": "openid email profile",
  "refresh_token": "<JWT — only if 'offline_access' was requested>"
}
```

- **`id_token`** — signed by Sudomimus's platform-wide OIDC key; verify against `https://oidc.sudomimus.com/.well-known/jwks.json`. Standard OIDC claims (`iss`, `sub`, `aud`, `exp`, `iat`, optional `nonce`, `email`, `name`).
- **`access_token`** — signed by the application's token-signing key, same shape as a native Connect access token.
- **`refresh_token`** — included only if you requested the `offline_access` scope.

### 3. Userinfo

```bash
curl https://oidc.sudomimus.com/userinfo \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

Returns the claims permitted by the granted scopes:

```json
{
  "sub": "<sector subject>",
  "email": "<only if 'email' scope was granted>",
  "name": "<only if 'profile' scope was granted>"
}
```

`sub` is the **sector subject** — the per-sector, application-visible identifier (the same value as the `id_token` `sub`), not the raw account UUID. `/userinfo` accepts both `GET` and `POST`.

### 4. Refresh

If you requested `offline_access` and got a refresh token, exchange it at `/token`:

```bash
curl -X POST https://oidc.sudomimus.com/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=refresh_token" \
  --data-urlencode "refresh_token=$REFRESH_TOKEN" \
  --data-urlencode "client_id=my-app"
  # plus client_assertion for confidential clients
```

You can optionally pass `scope` to request a narrowed subset of the originally-granted scopes. Per [OIDC §12.1](https://openid.net/specs/openid-connect-core-1_0.html#RefreshingAccessToken), the ID token from a refresh does not include a new `nonce`.

### 5. End session

```text
https://oidc.sudomimus.com/end-session
  ?id_token_hint=<id_token>
  &client_id=my-app
  &post_logout_redirect_uri=https%3A%2F%2Fapp.example.com%2F
  &state=<optional>
```

`/end-session` signals the end of an OIDC session and redirects to the `post_logout_redirect_uri` (which must match one of the registered URIs exactly). It does **not** revoke refresh tokens on its own — to invalidate the underlying session, call Connect's `/logout` (single session) or `/revoke-all` (every session for an account). See [Managing sessions](/en-us/guides/managing-sessions/).

## Using an OIDC library

Most modern OIDC libraries (Node's `openid-client`, Python's `authlib`, Java's `nimbus-jose-jwt`, etc.) discover everything from the issuer URL and handle PKCE, JWKS, and token verification automatically. A minimal Node example:

```js
import { Issuer } from "openid-client";

const issuer = await Issuer.discover("https://oidc.sudomimus.com");
const client = new issuer.Client({
    client_id: "my-app",
    redirect_uris: ["https://app.example.com/oidc/callback"],
    response_types: ["code"],
    token_endpoint_auth_method: "none",   // or "private_key_jwt"
});

// At login:
const codeVerifier = generators.codeVerifier();
const codeChallenge = generators.codeChallenge(codeVerifier);
const authUrl = client.authorizationUrl({
    scope: "openid email profile",
    code_challenge: codeChallenge,
    code_challenge_method: "S256",
    state, nonce,
});

// At callback:
const params = client.callbackParams(req);
const tokenSet = await client.callback(redirectUri, params, { code_verifier: codeVerifier, state, nonce });
const userinfo = await client.userinfo(tokenSet.access_token);
```

## Token verification reminder

OIDC ID tokens are verified against the JWKS at `oidc.sudomimus.com/.well-known/jwks.json` — that's the standard OIDC mechanism, and your library does it for you.

The `access_token` returned by `/token` is **not** a JWKS-verifiable token in the platform-wide sense; it's a per-application access token of the same shape as the Connect flow, verified against your application's `applicationPublicKey` from `POST /info`. If your library tries to verify the access token, point it at `/info` for the key, or simply treat the access token as opaque and use `/userinfo` for claims. See [Tokens and verification](/en-us/concepts/tokens-and-verification/).