Appearance
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:
- Use Authorization Code + PKCE with
code_challenge_method=S256. PKCE is required on every client;response_typemust becode. Implicit flow andresponse_type=tokenare not supported. - Discover endpoints from
{issuer}/.well-known/openid-configurationinstead of hardcoding them. - Send the user to
/authorize, where EntryIdP runs the face liveness check in its own UI. - Receive
code+stateat your registered redirect URI, verifystate, and exchange the code at/token. - Validate the ID token, then call
/userinfofor 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
/authorizeis a login (existing face)./authorize?prompt=createis enrolment (first-time face). See Topic A in each guide. - Mobile callbacks are custom URL schemes only — EntryIdP does not host
apple-app-site-associationorassetlinks.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.
| Platform | Library | Guide | AI prompt |
|---|---|---|---|
| Web — SPA React / Vue / Svelte | oidc-client-ts | Follow the guide | Prompt |
| Web — server-rendered Next.js / ASP.NET Core / Express | framework OIDC middleware | Follow the guide | Prompt |
| iOS native Swift | AppAuth-iOS | Follow the guide | Prompt |
| Android native Kotlin | AppAuth-Android | Follow the guide | Prompt |
| React Native Expo or bare | expo-auth-session / react-native-app-auth | Follow the guide | Prompt |
| Flutter | flutter_appauth | Follow the guide | Prompt |
| .NET MAUI iOS + Android | Duende.IdentityModel.OidcClient | Follow the guide | Prompt |
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_grantwitherror_description=refresh token replay detected). - Confidential clients must send the
client_secreton the refresh call as well as the code exchange. A refresh token on its own returnsinvalid_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_challengepresent,code_challenge_method=S256. - [ ]
stateis random per attempt, stored server-side (or in app state), and compared byte-for-byte on callback. - [ ]
nonceis sent and checked against the ID token (recommended). - [ ] Callback handles the
error=access_denied+error_description=user_not_registeredcase (enrol path). - [ ] No implicit flow, no
response_type=token, noclient_credentials.
Tokens
- [ ] ID token validated: signature (RS256 via JWKS
kid),iss,aud,exp,nonce;alg=nonerejected. - [ ] 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_tokenoverwrites the old one. - [ ] Confidential clients: the
client_secretis sent on the refresh call too, not only the code exchange. - [ ] If you call
/revokeor/introspect, each call sends yourclient_id(plusclient_secretwhen confidential) and only names your own tokens.
Assurance
- [ ]
face_liveness_verifiedis not treated as identity verification. For legal-identity decisions, requestidentity:verifiedand checkidentity_verifiedfrom/userinfo. - [ ]
email_verified/phone_number_verifiedare treated as alwaysfalse.
Need exact endpoint shapes, scope/claim lists, or error meanings? See the Reference.