---
name: bloksec-oidc-integration
description: Use when adding "Sign in with BlokSec" / BlokSec passwordless login to a web app, SPA, or backend via OpenID Connect (OIDC), or when debugging a BlokSec OIDC integration — token exchange, refresh tokens, ID token validation, or logout against the BlokSec identity provider.
---

# BlokSec OIDC Integration

## Overview

BlokSec is a passwordless identity provider with a standard OpenID Connect interface (built on `node-oidc-provider`). Integrate with the **authorization code flow** — confidential clients use a client secret; public clients (SPAs, native apps) use PKCE.

Configure your OIDC client library from the discovery document, but the concrete values below matter because BlokSec has several behaviors that **discovery does not reveal** (see [BlokSec-specific gotchas](#bloksec-specific-gotchas)). Get those wrong and login or refresh fails in ways generic OIDC knowledge won't catch.

## When to use

- Adding BlokSec passwordless login to a Node/React/any-framework app
- Configuring an OIDC client library (`oidc-client-ts`, `openid-client`, etc.) against BlokSec
- Debugging: no refresh token returned, ID token `iss` validation failing, access token won't decode, errors at signout

## Base URLs and discovery

| Environment | Base URL | Issuer (`iss`) | Discovery |
|-------------|----------|----------------|-----------|
| Production | `https://api.bloksec.io` | `https://api.bloksec.io/oidc` | `https://api.bloksec.io/oidc/.well-known/openid-configuration` |
| Sandbox | `https://api.bloksec.dev` | `https://api.bloksec.dev/oidc` | `https://api.bloksec.dev/oidc/.well-known/openid-configuration` |

Set your client library's `authority` / issuer to the **full issuer including `/oidc`**.

## Endpoints (verified, under `/oidc`)

| Endpoint | Path | Notes |
|----------|------|-------|
| Authorization | `/oidc/auth` | |
| Token | `/oidc/token` | |
| UserInfo | `/oidc/me` | **not** `/oidc/userinfo` |
| JWKS | `/oidc/jwks` | verifies **ID token** signatures |
| End session | `/oidc/session/end` | **not** `/oidc/end_session` |
| Introspection | `/oidc/token/introspection` | validate opaque access tokens |
| Revocation | `/oidc/token/revocation` | empty `200` on success (RFC 7009) — see gotchas |

## Scopes and claims

| Scope | Claims |
|-------|--------|
| `openid` (required) | `sub`, `aud`, `amr` |
| `email` | `email`, `email_verified` |
| `profile` | `name`, `given_name`, `family_name`, `preferred_username`, `picture`, `upn`, `user_did`, `user_short_code`, `account` |
| `phone` | `phone_number`, `phone_number_verified` |
| `offline_access` | grants a refresh token (requires `prompt=consent`, see gotchas) |

Use `sub` (a BlokSec DID) as the stable user key. `account` is a JSON-encoded object carrying the user's roles for the application.

## BlokSec-specific gotchas

These are the things generic OIDC knowledge and the discovery document will NOT tell you:

1. **Issuer carries the `/oidc` suffix.** The `iss` claim is `https://api.bloksec.io/oidc`, not the bare host. Set `authority` to the full issuer or ID token validation fails.
2. **Refresh tokens require BOTH `offline_access` AND `prompt=consent`.** Without `prompt=consent`, BlokSec silently drops `offline_access` and returns no refresh token. The application must also have the `refresh_token` grant type enabled.
3. **Access tokens are opaque, not JWTs.** Do not decode them. Validate via `/oidc/token/introspection` or `/oidc/me`. The **ID token** is the JWT — verify it via JWKS (`PS256`/`RS256`).
4. **Revocation success is an empty `200`** (RFC 7009; errors are JSON), which some libraries mishandle during signout. In `oidc-client-ts` set `revokeTokensOnSignout: false`; clear local tokens and redirect to `/oidc/session/end` instead.
5. **PKCE (`S256`) is required for public clients** (`token_endpoint_auth_method: none`); confidential clients use `client_secret_basic`.

## Quick integration steps

1. **Register the app** in the BlokSec admin console (Applications > Add Application). Set **Protocol = OpenID Connect**, then select the **Application Type**: **Web application** (confidential — gets a client secret), **Single-Page App** (public — PKCE, no secret), or **Native app** (public — PKCE, no secret). Add your redirect URI(s). For a Web application client, copy the client secret (shown once). Note the **Client ID**.
2. **Configure** your client from the discovery URL above; set `authority` to the full issuer.
3. **Authorization request** → `/oidc/auth` with `response_type=code`, `scope=openid email profile`, `state`, `nonce` (and PKCE `code_challenge`/`code_challenge_method=S256` for public clients). Add `offline_access` + `prompt=consent` if you need a refresh token.
4. **Exchange the code** at `/oidc/token` (`grant_type=authorization_code`, `code`, `redirect_uri`, `client_id`, plus `client_secret` for confidential or `code_verifier` for public).
5. **Validate the ID token**: signature via JWKS; `iss === https://api.bloksec.io/oidc`; `aud === client_id`; `exp` in future; `nonce` matches.
6. **Refresh** at `/oidc/token` with `grant_type=refresh_token` (only works if step 3 included `offline_access` + `prompt=consent`).
7. **Sign out**: clear local tokens, then redirect to `/oidc/session/end?id_token_hint=...&client_id=...&post_logout_redirect_uri=...`.

## SPA reference (oidc-client-ts)

Proven configuration (used by BlokSec's own admin console):

```typescript
import { UserManager, WebStorageStateStore } from 'oidc-client-ts';

const userManager = new UserManager({
  authority: 'https://api.bloksec.io/oidc',          // full issuer, incl. /oidc
  client_id: 'YOUR_CLIENT_ID',
  redirect_uri: `${window.location.origin}/auth/callback`,
  post_logout_redirect_uri: `${window.location.origin}/auth/signin`,
  response_type: 'code',
  scope: 'openid profile email offline_access',       // offline_access for refresh
  prompt: 'consent',                                   // REQUIRED to get a refresh token
  silent_redirect_uri: `${window.location.origin}/auth/silent-callback`,
  automaticSilentRenew: true,
  loadUserInfo: true,
  userStore: new WebStorageStateStore({ store: localStorage }),
  revokeTokensOnSignout: false,                        // libs mishandle the empty 200 revocation
});

// Signout: clear tokens, then redirect to the end-session endpoint
async function signOut() {
  await userManager.removeUser();
  window.location.href =
    'https://api.bloksec.io/oidc/session/end' +
    '?client_id=YOUR_CLIENT_ID' +
    `&post_logout_redirect_uri=${encodeURIComponent(window.location.origin + '/auth/signin')}`;
}
```

For a Node confidential client, `openid-client`'s `Issuer.discover('https://api.bloksec.io/oidc')` configures the endpoints and handles ID token validation, JWKS, and PKCE.

## Common mistakes

| Symptom | Cause / fix |
|---------|-------------|
| No `refresh_token` in token response | Missing `prompt=consent` and/or `offline_access`; or app lacks `refresh_token` grant |
| ID token `iss` validation fails | `authority`/expected `iss` set to bare host; use `https://api.bloksec.io/oidc` |
| Access token "is not a JWT" / won't decode | Access tokens are opaque; use introspection or `/oidc/me` |
| Errors at signout | Set `revokeTokensOnSignout: false`; use `/oidc/session/end` |
| 404 on UserInfo | Path is `/oidc/me`, not `/oidc/userinfo` |

## Full documentation

- Quickstart: https://docs.bloksec.com/developer/oidc-quickstart/
- Endpoint & claims reference: https://docs.bloksec.com/developer/oidc-reference/
