Skip to main content
Version: Latest (v0.0.1)

Login Portal

O2ID ships with an embedded portal that serves both the login page at /login and the consent page at /consent. No extra configuration is needed — it works out of the box when you run o2id serve.

Hosting the portal externally

You can decouple the portal from the O2ID binary and host it on any static web server or CDN — no need to build a portal yourself. Two steps:

1. Export the portal's static assets. export-portal writes the embedded portal (both the login and consent pages) to a directory, with asset paths rewritten to be relative so the files work from any location:

o2id export-portal ./portal --api-base-url https://auth.example.com

--api-base-url is O2ID's own base URL. It's baked into config.js so the exported pages post back to O2ID. Omit it only when the portal is served from the same origin as O2ID (e.g. behind a reverse proxy), in which case the pages fall back to their own origin at runtime.

Host the resulting ./portal directory wherever you like.

2. Point O2ID at the hosted portal. Set external_login_portal under [server] in o2id.toml:

[server]
external_login_portal = "https://login.example.com/login"

This single setting covers the whole portal. O2ID derives the consent page URL from it by swapping the last path segment (…/login…/consent), so both pages must be hosted together at those sibling paths — which is exactly how the exported bundle is laid out (login.html and consent.html side by side).

O2ID then redirects unauthenticated authorization requests to the login URL (passing a transaction_id), and consent-requiring authorizations to the consent URL (passing consent_id plus client_name and scope display hints). The pages post back to O2ID's POST /login and POST /consent endpoints respectively.

Because the pages talk to O2ID over stable contracts — POST /login (transaction_id, email, password) and POST /consent (consent_id, action) — keep the exported portal in step with your O2ID version when you upgrade, re-exporting it if either flow changes.

See Authorization Code Grant for how the portal fits into the full authorization flow.

Debugging the login/authorize/consent flow

If sign-in isn't behaving the way you expect — an unexpected access_denied, a scope that isn't granted, or a redirect that doesn't go where you thought — start the server with --debug to print verbose logs for every login, authorize, and consent decision:

o2id serve --debug

Or set it persistently in o2id.toml instead of passing the flag every run:

[server]
debug = true

This prints request-flow details — including email addresses and raw form data — to help pinpoint exactly which step diverged from what you expected (e.g. a hosted portal page posting action under a different field name than O2ID expects), so don't enable it in production.