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 public client, and on every confidential client unless an admin turns it off for a broker that can't send it, such as Amazon Cognito; 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.

Want to see the raw flow first? Code samples has short Node, Python and cURL scripts that sign in, check the ID token, and ask for a fresh face scan (step-up).

Already use Okta, Cognito or Google Workspace? ​

You don't need to change your app. Add EntryIdP to the service your people already sign in to, and it asks for a face scan instead of a password. No code is involved:


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.
  • [ ] The app copes with no email in /userinfo. It is released only once the person has verified it, so a missing one means "not verified".
  • [ ] email is re-read rather than cached against a user. It is bound to the verified value, so a changed address stops being released until the new one is verified.
  • [ ] phone_number_verified is read as a real value, not assumed false, and not cached against a number held separately. It is false for everyone until the mobile channel ships.
  • [ ] Nothing in the app assumes a sign-in returns a code without an intervening screen. EntryIdP may pause a sign-in after a successful face check to ask the person to confirm their email — never on prompt=none or a refresh, and never without eventually returning a working code to your redirect_uri. Treat email_verified as something that can change between sign-ins.

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

EntryIdP — Synapser