Skip to content

Powered by Grav + Helios

Authentication

Authentication

Endpoints for JWT login/logout, password reset, two-factor verification, first-run setup, and retrieving the current session. Token/refresh/revoke/2FA/setup/forgot/reset endpoints are public (no auth required). GET /me requires an authenticated session.

Endpoints for JWT login/logout, password reset, two-factor verification, first-run setup, and retrieving the current session. Token/refresh/revoke/2FA/setup/forgot/reset endpoints are public (no auth required). GET /me requires an authenticated session.

Get Token

POST /auth/token
Generate JWT access and refresh tokens. This is a public endpoint that does not require prior authentication.

Parameters

Name Type Description
username required string User account username
password required string User account password
JSON
{"username": "admin", "password": "password"}
JSON
{"data": {"access_token": "eyJ...", "refresh_token": "eyJ...", "token_type": "Bearer", "expires_in": 3600}}

Response Codes

200 Tokens generated
401 Invalid credentials

Refresh Token

POST /auth/refresh
Refresh an expired access token using a valid refresh token. This is a public endpoint that does not require prior authentication.

Parameters

Name Type Description
refresh_token required string A valid refresh token obtained from the token endpoint
JSON
{"refresh_token": "eyJ..."}

Response Codes

200 New tokens generated
401 Invalid or expired refresh token

Revoke Token

POST /auth/revoke
Revoke a refresh token (explicit logout). Best-effort decodes the token to record the user for the `onApiUserLogout` event, then revokes it unconditionally. Always returns 204, even if the token was already invalid — revoke is idempotent.

Parameters

Name Type Description
refresh_token required string The refresh token to invalidate (body field).
JSON
{"refresh_token": "eyJ..."}

Response Codes

204 Token revoked (or already invalid).
400 Missing refresh_token field.

Verify 2FA

POST /auth/2fa/verify
Exchange a 2FA challenge token plus a TOTP code for a full token pair. Returned when `/auth/token` responds with `requires_2fa: true`. The challenge token is single-use and expires in 5 minutes. On success, fires `onApiUserLogin` with `method: 2fa`.

Parameters

Name Type Description
challenge_token required string The short-lived challenge token returned from `/auth/token`.
code required string The 6-digit TOTP code from the user's authenticator app.
JSON
{"challenge_token": "eyJ...", "code": "123456"}
JSON
{"data": {"access_token": "eyJ...", "refresh_token": "eyJ...", "token_type": "Bearer", "expires_in": 3600}}

Response Codes

200 Verification succeeded; token pair issued.
400 Missing challenge_token or code.
401 Invalid/expired challenge token, or invalid 2FA code.
403 Account disabled, or 2FA support unavailable on the server.
429 Too many failed attempts; rate limited.

Forgot Password

POST /auth/forgot-password
Request a password reset email. Always returns a neutral success message regardless of whether the email matches an account — prevents account enumeration. Rate-limited per-user via the Login plugin's `pw_resets` bucket. Requires the Email and Login plugins configured.

Parameters

Name Type Description
email required string Email address of the account to reset.
admin_base_url optional string Origin + base path of the calling Admin2 client (e.g. `https://example.com/admin`). Used to construct the reset link in the email. Must be an `http`/`https` URL. Falls back to the `Referer` / `Origin` headers, then Grav's own root URL.
JSON
{"email": "[email protected]", "admin_base_url": "https://example.com/admin"}
JSON
{"data": {"message": "If an account exists for that email, a reset link has been sent."}}

Response Codes

200 Request accepted (neutral response regardless of match).
400 Missing email field.
429 Rate limit exceeded for this user.

Reset Password

POST /auth/reset-password
Complete a password reset using the token from the reset email. All failures return the same vague error message to prevent token probing from distinguishing bad user / wrong token / expired token. Rate-limited per-username. Fires `onApiPasswordReset` on success.

Parameters

Name Type Description
username required string Username of the account being reset.
token required string The reset token from the email link.
password required string The new password.
JSON
{"username": "admin", "token": "abc123...", "password": "new-secret"}
JSON
{"data": {"message": "Password reset successfully."}}

Response Codes

200 Password updated.
400 Missing required fields, or invalid/expired reset link.
429 Too many attempts; rate limited.

Setup Status

GET /auth/setup
Check whether the instance requires first-run setup. Returns `setup_required: true` only when `user/accounts/` is empty. Admin2 polls this on load to decide between showing the setup wizard or the login screen. Public (no auth required).
JSON
{"data": {"setup_required": true}}

Response Codes

200 Status returned successfully.

Create Initial User

POST /auth/setup
One-time first-run setup — creates the initial super-admin account on a fresh Grav 2.0 install. Active only while `user/accounts/` is empty; 409 thereafter. Grants `api.super` (not `admin.super`) by default so the account has API authority without implying classic-admin authority. Fires `onApiUserCreated` and `onApiSetupComplete`, then issues a login token pair so the client can skip straight to the dashboard. Public (no auth required), rate-limited per IP.

Parameters

Name Type Description
username required string Username (3–64 chars; letters, numbers, hyphens, underscores).
password required string Password (minimum 8 characters).
email required string A valid email address.
fullname optional string Display name for the account.
title optional string User title (defaults to "Administrator").
JSON
{"username": "admin", "password": "a-good-secret", "email": "[email protected]", "fullname": "Site Admin"}
JSON
{"data": {"access_token": "eyJ...", "refresh_token": "eyJ...", "token_type": "Bearer", "expires_in": 3600}}

Response Codes

200 Account created; token pair issued.
400 Validation failed (username format, invalid email, password too short, missing fields).
409 Setup has already been completed.
429 Too many setup attempts from this IP.

Get Current User

GET /me
Return the authenticated user's profile and resolved permissions. Admin2 calls this on every load to bootstrap the UI with the current identity, access map, running Grav core version, and active admin plugin version.
JSON
{"data": {"username": "admin", "fullname": "Site Admin", "email": "[email protected]", "avatar_url": "/user/accounts/avatars/admin.png", "super_admin": true, "access": {"api": {"access": true, "super": true}, "site": {"login": true}}, "content_editor": "", "grav_version": "2.0.0-beta.1", "admin_version": "3.0.0-beta.1"}}

Response Codes

200 Profile returned.
401 Not authenticated.
403 User lacks `api.access`.

The response includes:

  • access — the fully resolved permission map (inherited + direct), not the raw YAML. Use this instead of re-deriving permissions client-side.
  • grav_version — value of GRAV_VERSION on the server.
  • admin_version — version of the enabled admin plugin (checks admin2 first, then admin), read from its blueprints.yaml. null if neither is enabled.
  • content_editor — the user's preferred content editor (empty string if unset).