An Express + TypeScript app that exercises the SDK end to end across the full
demo contract. It imports the SDK via require(), so the runtime hits the
CommonJS half of the dual build (dist/cjs/); npm run check:exports at
the package root covers the ESM half.
Demo only. Cookies are unsigned, there is no CSRF protection, and the session map lives in process memory. Do not copy this into production.
The route table is generated from the shared SDK manifest, so every
LoginRadius SDK's demo exposes the same contract.
It is not hand-maintained here and cannot drift from the handlers: a route with
no implementation fails tsc.
Everything is on one page, as a flat list of titled cards — the same layout the Java, .NET and PHP demos use, so all five read as one product. It used to be three pages behind a tab strip; no route was dropped in the merge.
| Section | Covers |
|---|---|
| Register / Session | Register, log in, and the second-factor challenge |
| Forgot / Reset / Change password | Reset email, SMS OTP, reset by token, change password |
| Profile / Update profile | Profile read and update |
| Identifiers | Add and remove email, update phone |
| Delete account | Account deletion |
| Access token | Refresh, validate, active session |
| Multi-factor authentication | Settings, backup codes, authenticator enrolment |
| Passkey | Register and log in with a passkey |
Most authenticated routes read the demo session cookie and pass its access token
to the SDK. The three /api/mfa/login/* routes do not: they are
authenticated by the second-factor token a challenge login returned, held in a
separate cookie. Both names come from the manifest, so they cannot drift between
SDKs.
The session cookie carries an opaque id, not the access token itself. Refreshing rotates the access token upstream and invalidates the old one; an id lets that rotation happen server-side without the browser holding a stale credential.
- Passkey needs a secure context.
http://localhostqualifies, so the demo works as shipped; over plain HTTP on any other host the browser refuses. DELETE /api/accountdeletes the signed-in account only. The underlying operation is admin-scoped and would delete any address in the tenant, so the handler reads the signed-in profile first and refuses a mismatch. That guard is demo policy, not an SDK limitation.- Custom objects are currently disabled in the shared SDK manifest pending a tenant with a custom-object schema.
src/server.ts— bootstrap; registers every route from the generated table and applies its verb and auth mode uniformly.src/routes.generated.ts— generated. The route table, one input type per generated route, and the wrapper that decodes, validates and reports errors.src/calls.ts— the SDK call for each generated route. The part you copy.src/handlers.ts— the routes with real logic: SOTT, sessions, redirects, the delete-account guard, and the WebAuthn finish steps.src/session.ts— in-memory session store and cookie helpers.src/middleware/auth.ts— the two auth modes.public/— the UI:app.jsholds the shared fetch helpers and the WebAuthn encoding, and each page is plain HTML with no build step.
The wrapper around every SDK call is identical — decode a body, check required
fields, resolve a token, map an error to a status. Generating it from the
manifest keeps four languages' demos from drifting apart on any of that. The SDK
call itself stays hand-written in calls.ts, because that is the part a customer
actually copies and it should be readable, not generated out of sight.
cd demo
cp .env.example .env # fill in your tenant credentials
npm install
npm run dev # or: npm run build && npm startOpen http://localhost:3000.
See .env.example. Most important:
LR_API_KEY,LR_API_SECRET— your tenant credentials.LR_VERIFICATION_URL— where email verification links return to (usuallyhttp://localhost:3000while developing).LR_RESET_PASSWORD_URL— where password-reset links return to (http://localhost:3000/). The page reads the token off the query string and fills the Reset password card in.
src/lr.tsholds the singleton tenantLoginRadiusclient (apiKey + apiSecret) and auserClient(accessToken)helper that builds per-request bearer-token clients. Tenant client is for operations that act on behalf of the application; user clients are for endpoints that operate on the signed-in user.src/middleware/session.tswraps thehttpOnlycookie used for the session.secure: trueis enabled automatically inNODE_ENV=production.src/middleware/requireAuth.tsgates protected routes by reading the cookie and attachingreq.accessToken.src/server.tshas a single error-handling middleware that convertsLoginRadiusErrorinto a JSON response with the right HTTP status. TheisAuth() / isForbidden() / isRateLimit() / isServer()predicates surface on the response payload for the client to branch on.
- No social login / MFA / passkey flows (Phase-2 follow-up).
- No persistent app database — state is
LoginRadius+ anhttpOnlycookie. - No production-grade UI — vanilla HTML + minimal CSS by design.