For the complete documentation index, see llms.txt. This page is also available as Markdown.

/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/authnot 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).

Begin an authorization request (interactive login).

get

Stay logged in: opt-in per base config Loginkeep_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.

Query parameters
client_idstringRequired

The OAuth2 client identifier.

response_typestring · enumRequired

The OAuth2/OIDC response type. Combined types such as code id_token are also supported.

Possible values:
redirect_uristring · uriOptional

Where to redirect with the result. Must match the client registration.

scopestringOptional

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 Connect id_token (an RS256 JWT) alongside the access token. (The /api/oauth2/userinfo endpoint 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 both offline and the OIDC-standard offline_access for 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.
statestringOptional

Opaque value echoed back to the client.

code_challengestringOptional

PKCE code challenge.

code_challenge_methodstring · enumOptionalPossible values:
auth_methodstring · enumOptional

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:

  • easydblogin + password against the fylr user database (user types easydb, system, easydb_self_register). Also the fallback when auth_method is set to an unrecognised value. (An empty auth_method parses to auto, not easydb.)
  • ldaplogin + password bound against a configured LDAP directory; provisions/updates an ldap user. Only available when an LDAP connection is configured and the license permits external auth.
  • email — passwordless login for invited email users: login plus the account UUID (sent in the password field) — the UUID is the secret.
  • collectionlogin + password for collection user accounts.
  • saml — SAML 2.0 SSO; no credentials in the form. The first call redirects to the IdP, then the returned assertion provisions/updates an sso user. Only available when SAML is configured and the license permits external auth.
  • action_code — a one-time secret + action_code pair 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 config guest).
  • auto — server-side selector (the web-app default): resolves to anonymous when guests are allowed and no login is supplied, otherwise to password login (easydb, then ldap when configured).
Default: easydbPossible values:
loginstringOptional

User login (for password methods).

passwordstringOptional

User password (for password methods).

Responses
200

The HTML login form, rendered when credentials are absent or invalid (with a generic server.oauth2.LoginFailed message).

text/html
stringOptional
text

POST /oauth2/auth — Submit the login form / authorization request.

Submit the login form / authorization request.

post

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.

Body
client_idstringOptional
response_typestringOptional
redirect_uristring · uriOptional
scopestringOptional
statestringOptional
auth_methodstring · enumOptionalPossible values:
loginstringOptional
passwordstringOptional
keep_loginstring · enumOptional

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 (Loginkeep_login).

Possible values:
secretstringOptional

Action-code secret (for auth_method=action_code).

action_codestringOptional
Responses
200

HTML login form (on missing/invalid credentials).

text/html
stringOptional
text

POST /oauth2/token — Issue an access token.

Issue an access token.

post

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 authenticationAllow 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.

Body
grant_typestring · enumRequiredPossible values:
codestringOptional

Authorization code (grant_type=authorization_code).

redirect_uristring · uriOptional
refresh_tokenstringOptional

Refresh token (grant_type=refresh_token).

usernamestringOptional

Resource-owner login (grant_type=password).

passwordstringOptional

Resource-owner password (grant_type=password).

scopestringOptional

Space-separated scopes.

client_idstringOptional
client_secretstringOptional
code_verifierstringOptional

PKCE code verifier.

Responses
200

The issued token.

application/json
access_tokenstringOptional
token_typestring · enumOptionalPossible values:
expires_inintegerOptional

Seconds until the access token expires (86400 by default; configurable in the base config).

refresh_tokenstringOptional
scopestringOptional
id_tokenstringOptional

JWT id_token (RS256), present for the openid scope.

{
  "access_token": "8a1b2c3d…",
  "token_type": "bearer",
  "expires_in": 86400,
  "scope": "read write openid offline"
}

POST /oauth2/revoke — Revoke a token (logout).

Revoke a token (logout).

post
Body
tokenstringRequired

The access or refresh token to revoke.

token_type_hintstring · enumOptionalPossible values:
client_idstringOptional
client_secretstringOptional
Responses
200

The token was revoked (also returned for an unknown token).

No content

200

The token was revoked (also returned for an unknown token).

No content

POST /oauth2/introspect — Introspect a token.

Introspect a token.

post
Body
tokenstringRequired
token_type_hintstring · enumOptionalPossible values:
Responses
200

Introspection result. active is false for an unknown, expired or revoked token; the other fields are present only when the token is active.

application/json
activebooleanOptional
scopestringOptional
client_idstringOptional
substringOptional
expintegerOptional
iatintegerOptional
token_typestringOptional
200

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.

Get claims for the access-token user.

get
Authorizations
AuthorizationstringRequired

Access token in the Authorization header: Authorization: Bearer <token>.

Responses
200

The user's OpenID claims.

application/json

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).

substringRequired

Subject identifier — the user's login, or email, or the numeric user id if neither is set. Always present, not gated by the allowlist.

displaynamestringOptional

The user's compiled display name.

emailstringOptional

Email address. An empty string when the user has none (but the email claim is enabled).

email_verifiedbooleanOptional

Whether the email address has been confirmed. Returned alongside email.

first_namestringOptional
last_namestringOptional
companystringOptional
departmentstringOptional
phonestringOptional
address_supplementstringOptional
streetstringOptional
house_numberstringOptional
postal_codestringOptional
townstringOptional
statestringOptional
countrystringOptional
{
  "sub": "root",
  "displayname": "Root User",
  "email": "root@example.org",
  "email_verified": true
}

POST /oauth2/userinfo — Get claims for the access-token user (form POST).

Get claims for the access-token user (form POST).

post
Authorizations
AuthorizationstringRequired

Access token in the Authorization header: Authorization: Bearer <token>.

Responses
200

The user's OpenID claims.

application/json

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).

substringRequired

Subject identifier — the user's login, or email, or the numeric user id if neither is set. Always present, not gated by the allowlist.

displaynamestringOptional

The user's compiled display name.

emailstringOptional

Email address. An empty string when the user has none (but the email claim is enabled).

email_verifiedbooleanOptional

Whether the email address has been confirmed. Returned alongside email.

first_namestringOptional
last_namestringOptional
companystringOptional
departmentstringOptional
phonestringOptional
address_supplementstringOptional
streetstringOptional
house_numberstringOptional
postal_codestringOptional
townstringOptional
statestringOptional
countrystringOptional
200

The user's OpenID claims.

{
  "sub": "root",
  "displayname": "Root User",
  "email": "root@example.org",
  "email_verified": true
}

Last updated