Appearance
Web — SPA (React / Vue / Svelte)
A browser-only app with no backend. Use a public client (no client_secret) secured by PKCE.
Pick your path: Follow the guide below, or jump to the AI prompt.
Follow the guide
A — Login vs enrol
- Login:
userManager.signinRedirect()→ standard/authorize. - Enrol: the same call with
{ extraQueryParams: { prompt: 'create' } }→/authorize?prompt=create.
Choose one of:
- Option 1 (recommended): render two buttons — "Sign in" (login) and "Create account" (enrol with
prompt=create). Simplest, and new users skip a wasted round-trip. - Option 2: attempt login first; if the callback returns
error=access_deniedwitherror_description=user_not_registered, restart the flow withprompt=create.
B — Redirect callback setup
Your framework's router handles the redirect URI like any other route (e.g. /callback). On that route, call your library's signinRedirectCallback() to complete the exchange. No platform-specific work beyond registering the route's full URL as your redirect URI.
C — Secure token storage
| Token | Where |
|---|---|
| Access token | In memory only (a JS variable or library state) — never localStorage. |
| Refresh token | Do not request offline_access. SPAs cannot store refresh tokens safely; prompt for re-authentication when the access token expires. |
Use an AI prompt
Add EntryIdP biometric OIDC login to this SPA (React, Vue, or Svelte).
EntryIdP is an OpenID Connect provider. Users authenticate ONLY with a face liveness
check — no typed credentials, OTPs, or social logins. Do not add any sign-in form or credential-entry UI.
Issuer: https://idp-test.entryidp.com (use the issuer from my client registration; read
it from an env var, e.g. VITE_ENTRYIDP_ISSUER — never hardcode).
Before writing code:
1. Read package.json to confirm the framework and bundler (Vite, CRA, etc.).
2. Check for an existing OIDC library (oidc-client-ts). If none, install oidc-client-ts.
Implementation:
- Authorization Code + PKCE only. code_challenge_method=S256. Never use implicit flow or
response_type=token.
- This is a browser-only PUBLIC client: no client_secret in any file.
- Do NOT request offline_access — refresh tokens cannot be stored securely in a browser.
- Keep the access token in memory (UserManager state). Never use localStorage.
- Discover endpoints from {issuer}/.well-known/openid-configuration. Do not hardcode URLs.
- Redirect URI from VITE_ENTRYIDP_REDIRECT_URI; it must exactly match the registered URI.
Login vs enrol (EntryIdP-specific):
- Add a "Sign in" button → signinRedirect() (plain /authorize = login of an existing face).
- Add a "Create account" button → signinRedirect with extraQueryParams { prompt: 'create' }
(first-time face enrolment).
- In the callback, if the URL contains error=access_denied and
error_description=user_not_registered, send the user to the "Create account" flow.
Files: src/auth/entryidp.ts (UserManager config), an AuthProvider/context, a Callback
route that calls signinRedirectCallback(), Login/Logout buttons, and .env.example.
Guardrails:
- No sign-in form, OTP, or social-login UI.
- No client_secret anywhere.
- No offline_access scope; no refresh tokens.
- No implicit flow; endpoints fetched from discovery, not hardcoded.Done? Run through the pre-launch checklist before you ship, and see Refresh tokens rotate if you requested offline_access.