/api/oauth2
OAuth2 / OpenID Connect authorization endpoint (RFC 6749 §3.1). This is where the browser web-app logs a user in. It authenticates the end user — by password (auth_method=easydb, the default; also ldap, email, collection), anonymously (anonymous, when guest login is enabled), via SAML, or with an action code — and returns an authorization response (an authorization code, token or id_token) by redirecting back to the client's redirect_uri.
Served at /api/oauth2/auth — not under /api/v1. No prior access token is required.
Session binding (anti-hijacking)
Since fylr 6.34.0, an access/refresh token issued to the browser via this endpoint is bound to the browser's long-lived, HttpOnly fylr-browser-id cookie (a "Stay logged in" cookie is bound the same way). That cookie must then accompany the Authorization: Bearer token on every API request; a token presented from a different browser — i.e. without the matching cookie — is rejected as invalid (InvalidToken), so a stolen bearer token on its own cannot be replayed. Binding is silent and always on.
API clients are not affected: a token obtained at /oauth2/token via the password or client-credentials grant is issued unbound when the request carries no fylr-browser-id cookie — such integrations keep working unchanged. A login whose token is consumed on a different origin than the fylr itself — a third-party OAuth client (a cross-origin redirect_uri) or a cross-server webOnly frontend — is likewise issued an unbound token, since that origin's requests would not carry the cookie.
The refresh is bound too. Since fylr 6.34.4, /oauth2/refresh requires the cookie the session was bound to. Without it the refresh is rejected with InvalidToken instead of returning a new pair — a session that has lost its binding ends there rather than being renewed into an endless retry loop. Unbound sessions are unaffected, and a refresh presented together with the right cookie works as before. A client that meets InvalidToken should therefore discard the session and start a new login, not retry.
A cross-site login does not re-identify the browser. The fylr-browser-id cookie is SameSite=Lax and does not accompany a cross-site top-level POST — the assertion an external identity provider posts to /api/saml/acs is exactly that. Since fylr 6.34.4 such a request never mints a new browser id: the browser keeps the identity it already had, and the sessions bound to it survive the login. The same-site steps that follow the assertion carry the browser's own cookie and establish the identity for a browser that has none yet.
GET /oauth2/auth — Begin an authorization request (interactive login).
Stay logged in: opt-in per base config Login →
keep_login (off by default, or a session lifetime of 7 or 30
days). When enabled, the login form offers a "Stay logged in"
checkbox (keep_login=1); a successful login — including a passed
two-factor challenge — then additionally sets the persistent,
HttpOnly fylr-keep-login browser cookie. A later request to this
endpoint without credentials silently re-authorizes the cookie's
user (no login form, a fresh authorization response;
authenticated.method of such a session reports keep_login). The
lifetime slides on each use; /logout revokes the server-side
session and expires the cookie. When the option is off,
keep_login=1 is ignored and existing cookies are dropped.
Session binding (anti-hijacking): an access/refresh token issued
to the browser here is bound to the browser's long-lived, HttpOnly
fylr-browser-id cookie, and the keep-login cookie above is bound
the same way. That cookie must then accompany the
Authorization: Bearer token on every API request; a token — or a
keep-login cookie — presented from a different browser, i.e. without
the matching cookie, is rejected as invalid (InvalidToken), so a
stolen bearer token on its own cannot be replayed. Binding is silent
and always on. A login whose token is consumed on a different
origin than this fylr — a third-party OAuth client (a
cross-origin redirect_uri) or a cross-server webOnly frontend (a
cross-origin state redirect) — is issued unbound instead,
since that origin's requests would not carry the cookie.
The OAuth2 client identifier.
The OAuth2/OIDC response type. Combined types such as
code id_token are also supported.
Where to redirect with the result. Must match the client registration.
Space-separated scopes, within the client's allowed set (a
client that configures none may use all of read, write,
openid, offline). Their effect in fylr:
openid— also issue an OpenID Connectid_token(an RS256 JWT) alongside the access token. (The/api/oauth2/userinfoendpoint itself accepts any valid access token and is not gated on this scope.)offline— request a refresh token. A refresh token is issued (for the authorization-code and password grants) only when this scope is granted, and granting it also applies the configured refresh-token expiration override. fylr accepts bothofflineand the OIDC-standardoffline_accessfor this.read,write— capability labels only; they are not consulted to authorize API calls. Actual API authorization uses fylr's per-user/per-pool ACL rights, which are independent of these OAuth2 scopes despite the matching names.
Opaque value echoed back to the client.
PKCE code challenge.
fylr login method (default easydb). The endpoint accepts a
comma-separated list and tries the methods in order; the
web-app sends a single value. Each method authenticates a
specific user type:
easydb—login+passwordagainst the fylr user database (user typeseasydb,system,easydb_self_register). Also the fallback whenauth_methodis set to an unrecognised value. (An emptyauth_methodparses toauto, noteasydb.)ldap—login+passwordbound against a configured LDAP directory; provisions/updates anldapuser. Only available when an LDAP connection is configured and the license permits external auth.email— passwordless login for invitedemailusers:loginplus the account UUID (sent in thepasswordfield) — the UUID is the secret.collection—login+passwordforcollectionuser accounts.saml— SAML 2.0 SSO; no credentials in the form. The first call redirects to the IdP, then the returned assertion provisions/updates anssouser. Only available when SAML is configured and the license permits external auth.action_code— a one-timesecret+action_codepair used by internal redirect flows (forced password change, system messages); logs in whichever user the code was issued for. Single-use.anonymous— guest login, no credentials; binds to a per-browser anonymous user. Only when guest login is enabled (base configguest).auto— server-side selector (the web-app default): resolves toanonymouswhen guests are allowed and nologinis supplied, otherwise to password login (easydb, thenldapwhen configured).
easydbPossible values: User login (for password methods).
User password (for password methods).
The HTML login form, rendered when credentials are absent or
invalid (with a generic server.oauth2.LoginFailed message).
Redirect to the client redirect_uri carrying the
authorization code, token or id_token (per
response_type). May instead redirect (303) to an
interstitial page: two-factor authentication for enforced
users — /api/page/twoFactor (enter the email / authenticator
code), /api/page/twoFactorChoose (method picker),
/api/page/twoFactorSetup or /api/page/twoFactorSetupPasskey
(first-time enrollment) — or /api/page/setPassword (forced
password change) or /api/page/showMessage (pending tasks).
After a passed challenge the page redirects back here with
auth_method=action_code to complete the original request
without re-entering the password.
textPOST /oauth2/auth — Submit the login form / authorization request.
Same as GET, but the parameters are sent as an
application/x-www-form-urlencoded body — used when the login
form is submitted. The keep-login and session-binding notes on
GET apply here as well.
The "Stay logged in" checkbox. 1 mints the persistent
fylr-keep-login cookie after a fully completed login
(see the endpoint description). Ignored unless the base
config enables the feature (Login → keep_login).
Action-code secret (for auth_method=action_code).
HTML login form (on missing/invalid credentials).
Redirect to redirect_uri with the authorization result.
textPOST /oauth2/token — Issue an access token.
application/x-www-form-urlencoded request. Confidential clients
authenticate with client_id/client_secret (form fields or HTTP
Basic); public clients send only client_id. For the resource-owner
password grant fylr validates username/password against the
easydb, collection, email, then LDAP login methods in turn.
Two-factor authentication: the password grant cannot present a
second factor, so for users who are required to use 2FA it is
refused with invalid_grant (indistinguishable from a wrong
password) — unless the base config deliberately allows it
(Two-factor authentication → Allow password grant).
system:root is always exempt.
Session binding: a token minted here is bound to the
fylr-browser-id cookie when the request carries one (see the
Session binding note on the authorization endpoint's GET); the
password and client-credentials grants normally send no cookie and
so receive unbound tokens that work without it.
Authorization code (grant_type=authorization_code).
Refresh token (grant_type=refresh_token).
Resource-owner login (grant_type=password).
Resource-owner password (grant_type=password).
Space-separated scopes.
PKCE code verifier.
The issued token.
Seconds until the access token expires (86400 by default; configurable in the base config).
JWT id_token (RS256), present for the openid scope.
RFC 6749 error response (invalid_request, invalid_grant,
unsupported_grant_type, invalid_client, ...). fylr's
internal error is appended to error_description.
{
"access_token": "8a1b2c3d…",
"token_type": "bearer",
"expires_in": 86400,
"scope": "read write openid offline"
}POST /oauth2/revoke — Revoke a token (logout).
The access or refresh token to revoke.
The token was revoked (also returned for an unknown token).
No content
The token was revoked (also returned for an unknown token).
No content
POST /oauth2/introspect — Introspect a token.
Introspection result. active is false for an unknown,
expired or revoked token; the other fields are present only
when the token is active.
Introspection result. active is false for an unknown,
expired or revoked token; the other fields are present only
when the token is active.
{
"active": true,
"scope": "read write",
"sub": "root"
}GET /oauth2/userinfo — Get claims for the access-token user.
Access token in the Authorization header:
Authorization: Bearer <token>.
The user's OpenID claims.
OpenID Connect claims for the authenticated user, returned by
GET/POST /api/oauth2/userinfo. sub is always present; every
other claim is returned only when its name is listed in the instance's
userinfo allowlist (base-config OpenIdUserInfo).
Subject identifier — the user's login, or email, or the numeric user id if neither is set. Always present, not gated by the allowlist.
The user's compiled display name.
Email address. An empty string when the user has none (but the
email claim is enabled).
Whether the email address has been confirmed. Returned alongside
email.
No valid user could be resolved from the token. Code
InvalidToken (realm: api).
{
"sub": "root",
"displayname": "Root User",
"email": "root@example.org",
"email_verified": true
}POST /oauth2/userinfo — Get claims for the access-token user (form POST).
Access token in the Authorization header:
Authorization: Bearer <token>.
The user's OpenID claims.
OpenID Connect claims for the authenticated user, returned by
GET/POST /api/oauth2/userinfo. sub is always present; every
other claim is returned only when its name is listed in the instance's
userinfo allowlist (base-config OpenIdUserInfo).
Subject identifier — the user's login, or email, or the numeric user id if neither is set. Always present, not gated by the allowlist.
The user's compiled display name.
Email address. An empty string when the user has none (but the
email claim is enabled).
Whether the email address has been confirmed. Returned alongside
email.
The user's OpenID claims.
{
"sub": "root",
"displayname": "Root User",
"email": "root@example.org",
"email_verified": true
}Last updated