howdy/auth/routes
JSON endpoints for custom pages and API clients. Mount once at startup.
Values
pub fn api(
identity: auth.Auth,
at prefix: String,
) -> controller.Builder(Nil)
POST /login and /register deliver email tokens; POST /session exchanges
for a browser cookie; POST /token exchanges for a bearer token. Both take
{"token"}, or with auth.with_email_codes, {"email", "code"}.
POST /password/register sends verification; /password/session and
/password/token authenticate with email/password when enabled.
POST /password sets or replaces the caller’s password; see
auth.set_password for the fresh email-token session it requires.
The endpoints that take an email address also take an optional group
id, applied with auth.in_group; see auth.with_groups for when it is
needed. It is the client’s claim about where it wants to sign in, not
proof of membership: principal.user.group_id is the group to trust.
POST /locale sets the caller’s locale, {"locale": "de"}, or clears it
with {"locale": null}; see auth.set_locale.
GET /me, GET /sessions, POST /sessions/revoke and POST /logout accept either
transport, never both together. Cookie writes require Origin. Other
endpoints allow absent Origin for nonbrowser clients, but reject any
foreign/duplicate Origin.
Error bodies carry a stable code beside the English error, and
answers that tell the reader something carry one beside message, so a
client can show its own words; the bundled pages do. The codes are
unauthorized, forbidden, not_found, invalid, conflict,
validation, too_many_requests, unsupported_media_type and
internal from the status, the more specific password_incorrect,
password_unchanged, password_common, password_length, password_too_long,
email_invalid, email_unchanged, email_mismatch, email_taken,
mfa_enabled, passkey_limit, passkey_registered, passkey_name,
provider_linked, credential_too_large, locale_unsupported and
group_required, and for success token_sent, signup_token_sent,
approval_sent and confirmation_sent.
Clients are rate limited by socket address. Behind a reverse proxy that
is the proxy for everyone: use api_limited_by there.
pub fn api_limited_by(
identity: auth.Auth,
at prefix: String,
key key: fn(context.Context(Nil)) -> option.Option(String),
) -> controller.Builder(Nil)
As api, identifying clients for rate limiting with key. Behind a
trusted proxy, read the header it sets:
routes.api_limited_by(identity, at: "/api/auth", key: fn(ctx) {
request.get_header(ctx.request, "fly-client-ip") |> option.from_result
})
Only use a header your ingress overwrites; a client-supplied value lets
anyone choose their own bucket. None skips limiting for that request.
pub const credential_limit: Int
Requests per client per minute to endpoints that send email, exchange tokens or hash passwords.
pub fn providers(
identity: auth.Auth,
at prefix: String,
success_path success: String,
failure_path failure: String,
) -> controller.Builder(Nil)
Browser redirects for built-in identity providers. Mount once at startup.
The callback URI is auth.origin(identity) <> at <> "/providers/google/callback".
Destinations are fixed local paths. Login/link POSTs require an exact Origin.
pub fn providers_limited_by(
identity: auth.Auth,
at prefix: String,
success_path success: String,
failure_path failure: String,
key key: fn(context.Context(Nil)) -> option.Option(String),
) -> controller.Builder(Nil)
As providers, with a trusted client key for proxy-aware rate limits/audit.
pub const session_limit: Int
Requests per client per minute to the signed-in endpoints, which pages call routinely.
pub fn signed_in(
identity: auth.Auth,
ctx: context.Context(Nil),
res: response.Response(a),
session: auth.Session,
) -> response.Response(a)
Make session the browser’s active session on a response of your own,
setting the cookies the bundled routes set. For transports that obtain a
session some other way, such as auth.impersonate from an operator
tool. With multi-session enabled the session joins the browser’s
accounts; otherwise the cookie is replaced and the previous session is
left as it was.
pub fn sso(
identity: auth.Auth,
at prefix: String,
success_path success: String,
failure_path failure: String,
) -> controller.Builder(Nil)
Browser redirects for SSO connections; see howdy/auth/connection. Mount
once at startup: connections created later are served without remounting.
A connection’s redirect URI, to register at the customer’s provider, is
auth.origin(identity) <> at <> "/sso/" <> connection.id <> "/callback".
POST at <> "/sso/login" takes a form email and picks the connection by
its domain. Destinations are fixed local paths. POSTs require an exact
Origin.
pub fn sso_limited_by(
identity: auth.Auth,
at prefix: String,
success_path success: String,
failure_path failure: String,
key key: fn(context.Context(Nil)) -> option.Option(String),
) -> controller.Builder(Nil)
As sso, with a trusted client key for proxy-aware rate limits/audit.