Skip to content

Integrate your app

This is the implementation guide, organised by platform. Before you start you need a registered client — see Get a client.

Pick your path

Every platform guide offers two ways to integrate. They cover the same result — you don't need both.

Follow the guide — step-by-step, you write the code. Best if you want to understand what you're building, you're debugging an existing integration, or your stack isn't covered by a prompt.

Use an AI prompt — paste the prompt block into Claude, Cursor, or any agent. Best if you want a working skeleton fast and will review the output before shipping.

What every integration does

Regardless of platform, the shape is always the same:

  1. Use Authorization Code + PKCE with code_challenge_method=S256. PKCE is required on every client; response_type must be code. Implicit flow and response_type=token are not supported.
  2. Discover endpoints from {issuer}/.well-known/openid-configuration instead of hardcoding them.
  3. Send the user to /authorize, where EntryIdP runs the face liveness check in its own UI.
  4. Receive code + state at your registered redirect URI, verify state, and exchange the code at /token.
  5. Validate the ID token, then call /userinfo for profile/identity claims.

There are no typed credentials, OTPs, or social logins anywhere in this flow — the liveness check is the only credential.

Two EntryIdP-specific facts drive every platform guide:

  • Login vs enrol — a plain /authorize is a login (existing face). /authorize?prompt=create is enrolment (first-time face). See Topic A in each guide.
  • Mobile callbacks are custom URL schemes only — EntryIdP does not host apple-app-site-association or assetlinks.json, so Universal Links / App Links are not available.

Choose your platform

Pick your platform below. Each guide is self-contained: login-vs-enrol logic, redirect callback setup, secure token storage, and a copyable AI prompt.

PlatformLibraryGuideAI prompt
Web — SPA
React / Vue / Svelte
oidc-client-tsFollow the guidePrompt
Web — server-rendered
Next.js / ASP.NET Core / Express
framework OIDC middlewareFollow the guidePrompt
iOS
native Swift
AppAuth-iOSFollow the guidePrompt
Android
native Kotlin
AppAuth-AndroidFollow the guidePrompt
React Native
Expo or bare
expo-auth-session / react-native-app-authFollow the guidePrompt
Flutterflutter_appauthFollow the guidePrompt
.NET MAUI
iOS + Android
Duende.IdentityModel.OidcClientFollow the guidePrompt

Not listed? The shape above is the same for every OIDC library — start from the platform closest to yours and consult the Reference for exact endpoint shapes.


Refresh tokens rotate

Wherever you store a refresh token (server-side apps and mobile apps that requested offline_access):

  • Every refresh response contains a new refresh_token. Overwrite the stored one with it immediately.
  • Refresh tokens are single-use. Replaying an already-used token is treated as a compromise: EntryIdP revokes the entire token family and the user must re-authenticate (you'll get invalid_grant with error_description=refresh token replay detected).
  • Confidential clients must send the client_secret on the refresh call as well as the code exchange. A refresh token on its own returns invalid_client. A rejected refresh does not consume the token, so you can fix the credential and retry with the same one.

SPAs don't have this concern — they don't request offline_access.


Pre-launch checklist

Before going to production, confirm:

Configuration

  • [ ] Issuer points to the right environment; no endpoint URLs are hardcoded (all fetched from discovery).
  • [ ] Redirect URI and post-logout redirect URI exactly match registered values (scheme, path, trailing slash).
  • [ ] client_secret (confidential clients only) is in a secrets store / env var — never committed, never in browser or mobile code.

Authorization & callback

  • [ ] response_type=code, code_challenge present, code_challenge_method=S256.
  • [ ] state is random per attempt, stored server-side (or in app state), and compared byte-for-byte on callback.
  • [ ] nonce is sent and checked against the ID token (recommended).
  • [ ] Callback handles the error=access_denied + error_description=user_not_registered case (enrol path).
  • [ ] No implicit flow, no response_type=token, no client_credentials.

Tokens

  • [ ] ID token validated: signature (RS256 via JWKS kid), iss, aud, exp, nonce; alg=none rejected.
  • [ ] Tokens stored per the platform table above (in-memory for SPAs, secure OS storage for mobile, server-side for backends).
  • [ ] On every refresh, the new refresh_token overwrites the old one.
  • [ ] Confidential clients: the client_secret is sent on the refresh call too, not only the code exchange.
  • [ ] If you call /revoke or /introspect, each call sends your client_id (plus client_secret when confidential) and only names your own tokens.

Assurance

  • [ ] face_liveness_verified is not treated as identity verification. For legal-identity decisions, request identity:verified and check identity_verified from /userinfo.
  • [ ] email_verified / phone_number_verified are treated as always false.

Need exact endpoint shapes, scope/claim lists, or error meanings? See the Reference.

EntryIdP — Synapser