L LAB

Authentication

The auth module exposes two route trees that share controllers and config but issue different credentials:

  • App/api/v1/auth/app/* — Sanctum bearer tokens for native mobile / desktop clients.
  • Web/api/v1/auth/web/* — Sanctum cookie / session for browser SPAs (statefulApi() middleware).

Both expose the same operations: register, login, password reset, change password, OTP, OAuth and logout. Pick the tree that matches your client; the behaviour and config are identical.

Sub-features

TopicPage
Passwordless email codesOTP
OAuth / social loginSocial Auth
Signed-URL email verificationEmail Verification
Strong-password rulesPassword Policy
Brute-force throttlingRate Limiting
Roles & permissionsRBAC

Route reference

App authentication (token-based)

MethodEndpointAuth
POST/api/v1/auth/app/registerNo
POST/api/v1/auth/app/loginNo
POST/api/v1/auth/app/otpNo
POST/api/v1/auth/app/otp/verifyNo
POST/api/v1/auth/app/forgot-passwordNo
POST/api/v1/auth/app/reset-passwordNo
POST/api/v1/auth/app/logoutYes
POST/api/v1/auth/app/change-passwordYes

Web authentication (session-based)

Identical, with /auth/web/* instead of /auth/app/*. Logout destroys the session rather than revoking a token.

Shared

MethodEndpointDescriptionAuth
GET/api/v1/meAuthenticated user identityYes

Configuration

All auth knobs live under auth in config/boilerplate.php:

'auth' => [
    'password_auth_enabled' => true,
    'otp_auth_enabled' => true,

    'frontend_url' => env('FRONTEND_URL', 'http://localhost:3000'),
    'password_reset_url' => env('PASSWORD_RESET_URL', '/reset-password'),
    'password_reset_expiry_minutes' => 60,

    'socialite_enabled' => env('SOCIALITE_ENABLED', true),
    // ... password, otp, email_verification, rate_limit, notifications
],

Usage

Register (token)

curl -X POST http://localhost/api/v1/auth/app/register \
  -H "Content-Type: application/json" \
  -d '{
    "name": "John Doe",
    "email": "[email protected]",
    "password": "Password123",
    "password_confirmation": "Password123"
  }'
{
  "data": {
    "access_token": "1|abc123...",
    "token_type": "Bearer",
    "user": { "id": 1, "name": "John Doe", "email": "[email protected]" }
  }
}

Authenticated request

curl http://localhost/api/v1/me \
  -H "Authorization: Bearer 1|abc123..."

Web (SPA) login

await fetch('/api/v1/auth/web/login', {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ email: '[email protected]', password: 'Password123' }),
});

// Subsequent requests include the session cookie automatically.
const me = await fetch('/api/v1/me', { credentials: 'include' });

User schema

ColumnNotes
emailUnique
email_verified_atSet by email verification or OTP login
passwordBcrypt-hashed via Eloquent’s hashed cast
avatar_urlNullable
is_activeInactive accounts cannot log in
last_login_atUpdated on every login

Security model

ConcernHandling
Password storageBcrypt via hashed cast
Sanctum tokensHashed in the database
OAuth tokensEncrypted at rest
Inactive accountsLogin blocked when is_active = false
Brute forcePer-endpoint throttles (rate limiting)
Weak passwordsPassword::defaults() (password policy)
Email ownershipEmail verification — required-for-login is a flag

Customising

  • User creation: override findOrCreateUser in the social concern, or extend the controller register() methods.
  • Validation: form requests live in app/Http/Requests/Auth/ — follow the existing authorize() + rules() + messages() pattern.