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 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_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.
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_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. - [ ] The app copes with no
emailin/userinfo. It is released only once the person has verified it, so a missing one means "not verified". - [ ]
emailis 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_verifiedis read as a real value, not assumedfalse, and not cached against a number held separately. It isfalsefor 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=noneor a refresh, and never without eventually returning a working code to yourredirect_uri. Treatemail_verifiedas something that can change between sign-ins.
Need exact endpoint shapes, scope/claim lists, or error meanings? See the Reference.