The sign-in, device, error and status pages can be your own. The server owns the flow; your page collects input, keeps what it was handed and posts back. A working starter: example-login-page. Every error and status code with its default text: the console, System → Login pages → Page reference.
A bundle is a zip with index.html at its root, uploaded in the console under
System → Login pages. An upload is inactive until you preview and activate it.
Bundle <name> is served at /login/<name>/.
default serves every client without its own bundle, and holds the device.html,
status.html, error.html, files.html and account.html pages.default, then to the built-in page.Limits: 8 MB zipped, 50 MB unzipped, web assets only. Pages are served with
Content-Security-Policy: connect-src 'self'; frame-ancestors 'none'. A file you
leave out falls back to the built-in page. The built-in assets (/oauth/css/*,
/oauth/js/*, /oauth/images/*) are yours to reuse.
state backThe browser arrives at /login/<name>/?state=<opaque blob>. state is encrypted and
is the CSRF and replay protection. Copy it into a hidden field and post it, unchanged,
explicitly to /oauth/login — /login/ itself is GET-only.
POST state, username and password to /oauth/login.
When the app passed an OIDC login_hint, the URL carries it too
(?state=…&login_hint=…, trimmed, at most 256 characters): use it to pre-fill the
username. It's only a hint from the app, so let the person change it.
fetch with Accept: application/json and credentials: 'include': you stay
on your page and drive navigation.{"ok": true, "redirect": "<redirect_uri>?code=…&iss=…&state=…"}
{"ok": true, "form_post": {"url": "<redirect_uri>", "params": {…}}}
{"ok": false, "error": "credentials"}
On redirect, set window.location; on form_post, build and submit that form.
Errors are 400 with a code: credentials (prompt again), expired_code,
already_authorized, device_code_expired, ip_address, user_agent,
challenge, broken_flow, unknown.
"Continue with …" buttons. GET /oauth/federated/providers lists the active
identity providers (name, label). Each button is a link to
/oauth/federated/start?provider=<name>&state=<state>, with the same state. No
state in your URL means nobody is signing in: show no buttons.
Static pages, both in the default bundle. The server redirects to them with
everything in the query.
?error=<code> and ?secure=true|false; on secure=true, warn the person not to
enter credentials and show no login link. Some codes add context such as
client-name.?value=success|error|canceled, plus error,
flow, user, client_name and provider when they apply.Also in the default bundle. A browser that opens a private or missing file link
(/files/<id>, /files/path/...) is redirected here with ?error=unauthorized
and uri, the link it opened. Static and read-only, like error.html. Without it,
the built-in "Sign-in required" page answers with 401. Requests that aren't a
browser opening a page (no Accept: text/html, or an Authorization header) get
the JSON error as before.
Also in the default bundle. A browser opening /oauth/account is redirected
here, with done or error from the last action and the app's client_id and
return (when an app sent the user) in the query; without it, the
built-in account page answers. The page calls the account API
(OAUTH.md) with fetch: it is served from the engine's
own origin, so the sign-in cookie goes along and the cross-site check passes.
Signed out, GET /oauth/account answers 401 with login_url: send the browser
there, and it comes back to /oauth/account once signed in. Pass client_id and
return on to GET /oauth/account: when they check out, the answer has
back: {name, uri} for a "Back to " link.
Answers /oauth/device/activate twice:
user_code to /oauth/device/activate.?user_code=XXXX: show the code and POST action=confirm or
action=cancel to /oauth/device/activate. The browser carries a short-lived
SameSite=Lax cookie that the server set; no token in the page.<form method="post" action="/oauth/device/activate">
<p>Confirm code: <b id="code"></b></p>
<button name="action" value="confirm">Confirm</button>
<button name="action" value="cancel">Cancel</button>
</form>
<script>document.getElementById('code').textContent =
new URLSearchParams(location.search).get('user_code');</script>
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |