NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages

The package ships opt-in JSON endpoints per guard. Register them with routes.enabled = true for the guard, or explicitly:

// routes/api.php
use RoundlyConsulting\Auth\Facades\Authentication;

Authentication::routes('users');                                      // defaults

Authentication::routes('clients')
    ->prefix('api/clients/auth')
    ->name('clients.auth.')
    ->middleware(['api'])
    ->authenticatedMiddleware(['authentication.active'])
    ->except(['registration', 'invitations.manage']);

RouteRegistrar offers prefix(), name(), middleware(), authenticatedMiddleware(), only() and except(); it registers on destruct. Defaults: prefix {guard}/auth, names authentication.{guard}.*, middleware api. Registering a guard twice throws. Groups for only() / except(): login, challenge, tokens, registration, invitations, passwords, email, account, sessions, two-factor, passkeys, invitations.manage.

Endpoints

Every route carries authentication.guard:{guard} and Cache-Control: no-store, and exists only when its feature is enabled for the guard (a disabled feature is a 404). Tokens always travel in request bodies (JSON). * = authenticated (auth:{laravel_guard}); ? = optional.

MethodURI (under the prefix)BodyPurpose
POSTloginidentifier, passwordPassword login — identifier is any of identifier.columns (the email by default); there is no email field.
POSTlogin/magic-linkemailRequest a magic link.
POSTlogin/magic-link/consumetokenSign in with the link’s token.
POSTlogin/otpemailRequest an email code.
POSTlogin/otp/verifyemail, codeSign in with the code.
POSTlogin/passkey/options—Passwordless passkey options.
POSTlogin/passkeycredentialPasswordless passkey login.
POSTchallenge/two-factorchallenge_token, code, method? (totp | recovery_code)TOTP or recovery code (either kind is accepted under totp).
POSTchallenge/two-factor/enrolchallenge_tokenStart the forced TOTP enrolment (secret, QR code, recovery codes).
POSTchallenge/two-factor/enrol/confirmchallenge_token, codeConfirm it.
POSTchallenge/passkey/optionschallenge_tokenPasskey second-factor options.
POSTchallenge/passkeychallenge_token, credentialPasskey second factor.
POSTchallenge/passkey/enrol/optionschallenge_tokenForced passkey enrolment options.
POSTchallenge/passkey/enrolchallenge_token, credential, name?Forced passkey enrolment.
POSTrefreshrefresh_tokenRotate the refresh token (the previous access token stops working).
POSTregisteremail, password (when required), attributes?Registration (attributes = the host fields of registration.rules).
POSTinvitations/previewtokenWhat an invitation is for.
POSTinvitations/accepttoken, password (when required), email? (only when lock_email is off), attributes?Accept an invitation.
POSTpassword/forgotemailRequest a reset link.
POSTpassword/resettoken, passwordReset the password.
POSTemail/verifytoken, email (with the code channel)Verify an address — token is the link token or the code.
POSTemail/verification/resendemailResend verification (guest).
POSTemail/change/confirmtokenConfirm an email change (opened from the mail).
GET*me—The account.
PATCH*localelocale?, timezone?Locale / timezone.
POST*reauthenticatemethod + password | code | credentialRe-authentication — method is password, totp, recovery_code, email_otp or passkey.
POST*reauthenticate/passkey/options, reauthenticate/otp—Passkey options / mail a re-authentication code.
GET*activityquery per_page?Own login activity.
POST*logout, logout/others, logout/everywhere—Logouts.
GET* / DELETE*sessions, sessions/{session}—Device sessions.
PUT*passwordcurrent_password (when the account has one), passwordChange password.
POST*email/changeemailRequest an email change.
POST*email/verification—Send verification.
GET* POST* DELETE*two-factor, two-factor/recovery-codes—Status, start enrolment, disable, regenerate recovery codes.
POST*two-factor/confirmcodeConfirm the enrolment.
GET* POST*passkeys, passkeys/optionspasskeys: credential, name?List, registration options, register.
PATCH* / DELETE*passkeys/{passkey}PATCH: nameRename / remove.
GET*invitationsquery status?, per_page?Invitation admin (Gate ability).
POST*invitationsemail, payload?, locale?, ttl? (seconds), send? (default true)Create — 201 with data and the link as url (shown once; with send: false nothing is mailed and you deliver it).
POST* / DELETE*invitations/{invitation}/resend, invitations/{invitation}—Resend ({"status": "sent", "url": …}) / revoke.

Every endpoint that signs in also takes device_name? (shown in the session list); register and invitations/accept also take timezone? (stored on the new account). The device is read from the X-Device-Id header (activity.new_device.header), the locale from X-Locale / Accept-Language. credential is the browser’s PublicKeyCredential JSON with base64url members, plus the ceremonyId of the options it answers.

Responses

// 200 — authenticated
{ "status": "authenticated", "token_type": "Bearer", "access_token": "eyJ…", "expires_in": 900,
  "expires_at": "2026-09-26T10:15:00Z", "refresh_token": "…", "refresh_expires_at": "2026-10-26T10:00:00Z",
  "session_id": "0199…" }

// 200 — challenge
{ "status": "challenge", "challenge_token": "…", "expires_at": "…", "attempts_left": 5, "method": "password",
  "completed": [], "remaining": [ { "step": "second_factor", "methods": ["totp", "recovery_code", "passkey"] } ] }

// 202 — enumeration-safe acknowledgement (magic link, email code, forgot, resend, email change)
{ "status": "sent" }

// error
{ "message": "…", "code": "invalid_credentials", "errors": { "identifier": ["…"] } }

Credential-change responses (PUT password, POST two-factor/confirm, DELETE two-factor, POST two-factor/recovery-codes, POST passkeys, DELETE passkeys/{passkey}) include tokens or null. When non-null the client must swap to it immediately — its previous access token died with the change.

Errors

CodeStatusWhen
invalid_credentials422Wrong credentials (uniform).
invalid_token / invalid_code422An emailed link or code is invalid (uniform).
challenge_invalid / factor_failed / factor_not_allowed422Challenge problems (attempts_left on factor_failed).
invalid_invitation422An invitation is invalid (uniform).
passkey_registration_failed422A passkey did not register.
refresh_invalid401The refresh token is invalid (uniform).
account_disabled / email_not_verified / enrolment_required / reauthentication_required403Account state or policy (methods on reauth).
registration_closed / invitation_required / login_denied403Registration or risk.
account_locked423A re-authenticating account is locked.
too_many_attempts429Throttled or hard-locked (Retry-After).
two_factor_required / last_credential / two_factor_already_enabled / two_factor_not_enabled409Refused changes.
method_disabled / not_found404Feature off or unknown id.
a validation error on password422Breached-password service down with fail_closed.
misconfigured500Configuration error (detail only with app.debug).

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.