Agrofin

Admin panel authentication

On this page 5

The admin panel (a separate React SPA, served from its own domain) does not use the Bearer-token flow described in Getting started. It authenticates with an httpOnly session cookie via Laravel Sanctum's SPA support, so the token never touches browser JavaScript.

The three-step flow

  1. GET /sanctum/csrf-cookie — call this once before the first state-changing request (and again after the session ends). It sets the XSRF-TOKEN cookie; your HTTP client (axios, fetch) must read it and echo it back as the X-XSRF-TOKEN header on every subsequent request. Axios does this automatically when withCredentials: true is set.
  2. POST /api/v1/admin/login — send email + password. On success the response sets the session cookie (Set-Cookie) and returns the profile below. On failure it behaves exactly like a bad-credentials response — no distinction between "wrong password" and "no such account", and an inactive account is rejected the same way.
  3. Every request after that must be sent with withCredentials: true (or the cookie-equivalent for your HTTP client) so the browser attaches the session cookie. No Authorization header is needed or used.

This only works from a domain listed in SANCTUM_STATEFUL_DOMAINS — any other origin falls back to stateless (bearer-token) handling and cookie login will silently not authenticate it.

Endpoints

Endpoint Auth Notes
POST /api/v1/admin/login email, password; sets the session cookie; throttled 10/min
POST /api/v1/admin/logout Session invalidates the session and rotates the CSRF token
GET /api/v1/admin/me Session current admin's profile and permissions

AdminUserResource fields

GET /api/v1/admin/me and a successful POST /api/v1/admin/login both return this shape under data, in this exact order:

Field Type Notes
id number
name string
email string
avatarUrl string | null null when no avatar media is attached
isSuperadmin boolean true only for the super_admin role — the panel can use this to unlock every screen without checking individual permissions
permissions string[] flat, deduplicated list of every permission the user's roles grant (a super_admin gets the full permission list)
roles string[] role names assigned to the user

The panel is expected to drive its own routing and UI visibility off isSuperadmin and permissions — never off roles directly, since role-to- permission mapping can change without a frontend release.

Error responses

HTTP When
401 login with wrong/unknown credentials or an inactive account; me/logout with no active session
403 Authenticated but the action is not permitted (enforced per-endpoint, not by these three routes)
429 More than 10 login attempts per minute from the same client, or the email+IP brute-force guard (5 failed attempts / 15 minutes) has tripped — the message carries the retry-after in seconds

Brute-force protection

POST /api/v1/admin/login is guarded twice:

  • A route-level rate limit of 10 requests/minute per client (throttle:10,1).
  • A slower email+IP lockout: 5 failed attempts lock that email+IP pair out for 15 minutes, independent of the route throttle. A successful login clears the counter immediately.

Both limits return 429 with the same localized "too many attempts" message.