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

# OIDC Integration Guide

> Implement Hopae Connect using the standard OIDC Authorization Code flow with PKCE

## Overview

This guide shows you how to integrate Hopae Connect with the standard OpenID Connect (OIDC) Authorization Code flow. You will redirect users to the Hopae OpenID Provider (issuer: `https://connect.hopae.com`), receive an authorization code on your redirect URI, and exchange it for tokens using the OIDC token endpoint. For a shorter walkthrough, start with the [Quickstart Guide](/guides/getting-started/quickstart).

## How It Works

```mermaid theme={null}
sequenceDiagram
    participant User
    participant YourApp
    participant HopaeOP as Hopae OP (/auth)
    participant Provider

    User->>YourApp: Request verification
    YourApp->>HopaeOP: Redirect to /auth (client_id, scope, state, PKCE)
    HopaeOP->>User: Present verification UI
    User->>Provider: Complete verification
    Provider->>HopaeOP: Return verified data
    HopaeOP->>YourApp: Redirect to redirect_uri with authorization code
    YourApp->>HopaeOP: POST /token (code, client auth, PKCE)
    HopaeOP->>YourApp: Return ID & access tokens
```

## Console Configuration

### Redirect URI Management

Configure your redirect URIs in the [Hopae Console](https://console.hopae.com):

1. **Open your application** in the Console.
2. **Navigate to Developer settings** and add each redirect URI you expect to use (production, staging, mobile deep links, etc.).

### Authorization Request Construction

Use the `/auth` endpoint on the issuer domain (`https://sandbox.connect.hopae.com/auth` for sandbox, `https://connect.hopae.com/auth` for production):

```javascript theme={null}
const authorizationEndpoint = "https://sandbox.connect.hopae.com/auth"; // Sandbox
const redirectUri = "https://localhost:3000/callback";

const params = new URLSearchParams({
  client_id: "YOUR_CLIENT_ID",
  redirect_uri: redirectUri,
  response_type: "code",
  scope: "openid idv",
});

if (codeChallenge) {
  params.set("code_challenge", codeChallenge);
  params.set("code_challenge_method", "S256");
}

const authorizationUrl = `${authorizationEndpoint}?${params.toString()}`;
```

### Authorization Request Parameters

| Parameter               | Required    | Description                                                        | Example                                       |
| ----------------------- | ----------- | ------------------------------------------------------------------ | --------------------------------------------- |
| `client_id`             | Yes         | Your Hopae Connect client identifier                               | `5SZdu0fn`                                    |
| `response_type`         | Yes         | Must be `code`                                                     | `code`                                        |
| `redirect_uri`          | Yes         | Whitelisted callback URI                                           | `https://localhost:3000/callback`             |
| `scope`                 | Yes         | Include `openid`; add `idv` to receive normalized identity data    | `openid idv`                                  |
| `nonce`                 | Recommended | Replay protection for ID tokens (especially browser-based clients) | `4d9961bd-12a9-46d0-803f-aafef1bf814d`        |
| `code_challenge`        | Conditional | PKCE code challenge (required for public clients)                  | `E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM` |
| `code_challenge_method` | Conditional | Must be `S256` when `code_challenge` is provided                   | `S256`                                        |
| `prompt`                | Optional    | Force a specific UX path (`login`, `consent`, `select_account`)    | `login`                                       |
| `acr_values`            | Optional    | Request verification constraints. See format below                 | `urn:hopae:loa:3`                             |

### acr\_values Format

The `acr_values` parameter accepts space-separated URN values to control verification:

| URN Type | Format                      | Description                                                            |
| -------- | --------------------------- | ---------------------------------------------------------------------- |
| LoA      | `urn:hopae:loa:{level}`     | Request minimum [Level of Assurance](/guides/concepts/assurance) (1–5) |
| Provider | `urn:hopae:id:{providerId}` | Filter to specific identity providers                                  |

**Examples:**

```
# Request minimum LoA 3
acr_values=urn:hopae:loa:3

# Filter to specific provider
acr_values=urn:hopae:id:bankidse

# Combine LoA and provider
acr_values=urn:hopae:loa:3 urn:hopae:id:mitid

# Multiple providers (comma-separated)
acr_values=urn:hopae:id:mitid,bankidse
```

## PKCE Support

PKCE (Proof Key for Code Exchange) is strongly recommended for native and SPA clients.

```javascript theme={null}
function base64UrlEncode(buffer) {
  return btoa(String.fromCharCode(...buffer))
    .replace(/=+/g, "")
    .replace(/\+/g, "-")
    .replace(/\//g, "_");
}

export function generateVerifier() {
  const randomBuffer = crypto.getRandomValues(new Uint8Array(32));
  return base64UrlEncode(randomBuffer);
}

export async function generateChallenge(verifier) {
  const data = new TextEncoder().encode(verifier);
  const digest = await crypto.subtle.digest("SHA-256", data);
  return base64UrlEncode(new Uint8Array(digest));
}

const codeVerifier = generateVerifier();
const codeChallenge = await generateChallenge(codeVerifier);
sessionStorage.setItem("code_verifier", codeVerifier);
```

<Tip>
  Store the `code_verifier` securely (session storage, encrypted cookie, etc.) so you can supply it during the token exchange.
</Tip>

## Callback Handling

After the user completes verification, Hopae redirects to your `redirect_uri` with either an authorization code or an error.

**Success Response:**

```
https://localhost:3000/callback?code=auth_abc123&state=b2a6c120-8d07-4f8f-a54f-ff832c3772b3
```

**Error Response:**

```
https://localhost:3000/callback?error=access_denied&error_description=User%20cancelled&state=b2a6c120-8d07-4f8f-a54f-ff832c3772b3
```

Validate the `state` value before proceeding.

## Token Exchange

Exchange the authorization code for tokens using the OIDC `/token` endpoint.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://sandbox.connect.hopae.com/token \
    -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "grant_type=authorization_code&code=auth_abc123&redirect_uri=https%3A%2F%2Flocalhost%3A3000%2Fcallback&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
  ```

  ```javascript Node.js theme={null}
  const response = await fetch("https://sandbox.connect.hopae.com/token", {
    method: "POST",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
      Authorization: "Basic " + btoa(`${CLIENT_ID}:${CLIENT_SECRET}`),
    },
    body: new URLSearchParams({
      grant_type: "authorization_code",
      code: authCode,
      redirect_uri: redirectUri,
      code_verifier: sessionStorage.getItem("code_verifier"),
    }),
  });

  const { access_token, id_token } = await response.json();
  ```

  ```python Python theme={null}
  import requests
  from base64 import b64encode

  credentials = b64encode(f"{CLIENT_ID}:{CLIENT_SECRET}".encode()).decode()

  response = requests.post(
      'https://sandbox.connect.hopae.com/token',
      headers={
          'Authorization': f'Basic {credentials}',
          'Content-Type': 'application/x-www-form-urlencoded'
      },
      data={
          'grant_type': 'authorization_code',
          'code': auth_code,
          'redirect_uri': redirect_uri,
          'code_verifier': session.get('code_verifier')
      }
  )

  token_response = response.json()
  id_token = token_response['id_token']
  ```
</CodeGroup>

## ID Token Claims

Wondering what data you’ll get back? See the Return Data Model for normalized claims, assurance, issuers, presentation, and evidence.

<a href="/guides/concepts/return-data-model">Explore the Return Data Model →</a>

## Mobile Integration

You can initiate the OIDC flow from native apps using platform browser sessions (ASWebAuthenticationSession, Custom Tabs, etc.). The Expo example below demonstrates the pattern:

```javascript theme={null}
import * as AuthSession from "expo-auth-session";

const issuer = "https://sandbox.connect.hopae.com";

export async function startVerification() {
  const discovery = await AuthSession.fetchDiscoveryAsync(issuer);
  const redirectUri = AuthSession.makeRedirectUri({ useProxy: false });

  const authRequest = new AuthSession.AuthRequest({
    clientId: CLIENT_ID,
    redirectUri,
    responseType: AuthSession.ResponseType.Code,
    scopes: ["openid", "profile"],
    usePKCE: true,
  });

  const result = await authRequest.promptAsync(discovery);

  if (result.type === "success" && authRequest.code) {
    // Exchange authRequest.code on your secure backend with /token
  }
}
```

<Info>
  On iOS, `AuthSession` uses `ASWebAuthenticationSession`; on Android it launches a Custom Tab. This keeps user credentials within trusted system components while preserving the OIDC redirect semantics.
</Info>

## Common Issues and Solutions

<AccordionGroup>
  <Accordion title="invalid_redirect_uri">
    * Ensure the URI is listed in your Console allow list (exact match).
    * URL-encode the value when placing it on the query string.
    * Confirm you're using the correct environment (`sandbox` vs production).
  </Accordion>

  <Accordion title="invalid_grant">
    * Authorization codes expire in 5 minutes and are single-use.
    * Verify the `code_verifier` matches the original `code_challenge`.
    * Confirm you are targeting the correct issuer when exchanging the code.
  </Accordion>

  <Accordion title="State or nonce validation failures">
    * Persist the generated values securely between request and callback.
    * Reject callbacks where `state` or `nonce` is missing or mismatched.
    * Log mismatches (without sensitive data) for investigation.
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="API Integration" icon="code" href="/guides/api-integration">
    Build custom verification experiences with direct API calls
  </Card>

  <Card title="Verification Flow" icon="flow-chart" href="/guides/concepts/verification-flow">
    Understand every step in the verification lifecycle
  </Card>
</CardGroup>
