Authentication Setup

Scaffold a complete authentication system with login, register, dashboard, settings, and role management using a single command.

Overview

The auth:setup command generates a fully-functional authentication system into your project, including:

  • User model with Doctrine ORM, password hashing, and remember tokens
  • Login & Register controllers and pages
  • Dashboard at /v1/dashboard with sidebar layout
  • Settings page with profile and password management
  • Role management (if authorization module is installed)
  • Database migrations for users and roles tables
  • Dark-themed UI matching the starter template

Quick Start (Auto Setup)

The fastest way to get authentication running. When you add the database module, you'll be offered an auto setup option:

Terminal
php craftsman add database

After the database module installs, you'll see:

$ php craftsman add database

Adding module: database
Running: composer require zephyrphp/database
...
Module 'database' has been installed and enabled.

Would you like to configure the database connection now? (yes/no) [yes]: yes
...

Authentication System
---------------------
  Set up a complete auth system with:
    - User model with roles
    - Login & Register pages
    - Dashboard (/v1/dashboard)
    - Settings page (profile, password, roles)

How would you like to proceed?:
  [0] Auto setup (Recommended) — Install auth + authorization and scaffold everything
  [1] Manual — I'll add modules myself later

Choosing Auto setup will:

  1. Install the zephyrphp/auth module
  2. Install the zephyrphp/authorization module
  3. Run auth:setup to scaffold everything
One Command Setup

Auto setup installs both authentication and authorization modules, then scaffolds the entire auth system. You'll have a working login, register, and dashboard in under a minute.

Manual Setup

If you prefer step-by-step control, install modules individually:

Terminal
# Step 1: Add the database module (if not already added)
php craftsman add database

# Step 2: Add the auth module
php craftsman add auth

# Step 3: (Optional) Add authorization for role management
php craftsman add authorization

# Step 4: Run the scaffold
php craftsman auth:setup

auth:setup Command

The scaffolding command that generates all authentication files.

Command
php craftsman auth:setup

Prerequisites

Before running auth:setup, ensure you have:

  • Database module installed and enabled: php craftsman add database
  • Auth module installed and enabled: php craftsman add auth
  • Database connection configured: php craftsman db:setup

Authentication Type Selection

The wizard asks which authentication type you want:

Authentication Type
-------------------
Select authentication type:
  [0] Session-based authentication (traditional)
  [1] JWT Token authentication (API)
  [2] Both (Session + JWT)
Type Best For How It Works
Session Web apps, server-rendered pages Stores user ID in server-side session, cookie-based
JWT APIs, SPAs, mobile apps Stateless tokens in Authorization header
Both Full-stack apps with API Session for web, JWT for API endpoints

Generated Files

The command generates the following files into your project, using your app's namespace (detected from composer.json):

Models

FileDescription
app/Models/User.php Doctrine entity extending Model, implements AuthenticatableInterface
app/Models/Role.php (if authorization) Role entity with name, slug, description

Controllers

FileMethods
app/Controllers/Auth/LoginController.php showLoginForm(), login(), logout()
app/Controllers/Auth/RegisterController.php showRegisterForm(), store()
app/Controllers/Dashboard/DashboardController.php index()
app/Controllers/Dashboard/SettingsController.php index(), updateProfile(), updatePassword(), updateRoles()

Twig Templates

FileDescription
pages/auth/login.twigLogin form with email, password, remember me
pages/auth/register.twigRegistration form with name, email, password, confirmation
pages/layouts/dashboard.twigDashboard layout with sidebar, topbar, user info
pages/dashboard/index.twigDashboard home with welcome message and stats
pages/dashboard/settings.twigSettings with Profile, Password, and Roles tabs

Other Files

FileDescription
public/assets/css/dashboard.cssDashboard and auth page styles (dark theme)
database/migrations/..._create_users_table.phpUsers table migration
database/migrations/..._create_roles_tables.php (if authorization)Roles + role_user pivot table migration
routes/web.php (appended)Auth and dashboard route definitions

Generated Routes

The following routes are appended to your routes/web.php:

Authentication Routes

MethodURIActionMiddleware
GET /login Show login form GuestMiddleware
POST /login Authenticate user GuestMiddleware
GET /register Show register form GuestMiddleware
POST /register Create account GuestMiddleware
POST /logout Logout user

Dashboard Routes

All dashboard routes are grouped under /v1/dashboard and protected by AuthMiddleware:

MethodURIAction
GET /v1/dashboard Dashboard home
GET /v1/dashboard/settings Settings page
POST /v1/dashboard/settings/profile Update profile (name, email)
POST /v1/dashboard/settings/password Change password
POST /v1/dashboard/settings/roles Update roles (if authorization)
// routes/web.php (generated)

use App\Controllers\Auth\LoginController;
use App\Controllers\Auth\RegisterController;
use App\Controllers\Dashboard\DashboardController;
use App\Controllers\Dashboard\SettingsController;
use ZephyrPHP\Middleware\AuthMiddleware;
use ZephyrPHP\Middleware\GuestMiddleware;

// Guest routes
Route::get('/login', [LoginController::class, 'showLoginForm'])->middleware([GuestMiddleware::class]);
Route::post('/login', [LoginController::class, 'login'])->middleware([GuestMiddleware::class]);
Route::get('/register', [RegisterController::class, 'showRegisterForm'])->middleware([GuestMiddleware::class]);
Route::post('/register', [RegisterController::class, 'store'])->middleware([GuestMiddleware::class]);
Route::post('/logout', [LoginController::class, 'logout']);

// Dashboard (authenticated)
Route::group(['prefix' => '/v1/dashboard', 'middleware' => [AuthMiddleware::class]], function () {
    Route::get('/', [DashboardController::class, 'index']);
    Route::get('/settings', [SettingsController::class, 'index']);
    Route::post('/settings/profile', [SettingsController::class, 'updateProfile']);
    Route::post('/settings/password', [SettingsController::class, 'updatePassword']);
});

User Model

The generated User model extends ZephyrPHP\Database\Model and implements the authentication interface:

<?php

namespace App\Models;

use Doctrine\ORM\Mapping as ORM;
use ZephyrPHP\Database\Model;
use ZephyrPHP\Auth\AuthenticatableInterface;
use ZephyrPHP\Auth\Authenticatable;
use ZephyrPHP\Authorization\Traits\HasRoles; // if authorization module

#[ORM\Entity]
#[ORM\Table(name: 'users')]
#[ORM\HasLifecycleCallbacks]
class User extends Model implements AuthenticatableInterface
{
    use Authenticatable;
    use HasRoles; // if authorization module

    #[ORM\Column(type: 'string', length: 255)]
    protected string $name = '';

    #[ORM\Column(type: 'string', length: 180, unique: true)]
    protected string $email = '';

    #[ORM\Column(type: 'string', length: 255)]
    protected string $password = '';

    #[ORM\Column(type: 'string', length: 100, nullable: true)]
    protected ?string $rememberToken = null;

    // Getters and setters generated automatically...
}
Namespace Detection

The generated code uses your project's namespace from composer.json. If your app is named MyApp, the User model will use namespace MyApp\Models; instead of App\Models.

Using Authentication

Check if Authenticated

<?php

use ZephyrPHP\Auth\Auth;

// In a controller
if (Auth::check()) {
    $user = Auth::user();
}

// Using helpers
if (auth()->check()) {
    $user = user();
}

Login with Credentials

<?php

if (Auth::attempt(['email' => $email, 'password' => $password], $remember)) {
    // Success - user is now authenticated
    return $this->redirect('/v1/dashboard');
}

// Failed - credentials don't match

In Twig Templates

    <a href="/login">Login</a>

Manual Login/Logout

<?php

// Login a user directly (without credentials)
Auth::login($user);

// Login with remember me
Auth::login($user, remember: true);

// Login by ID
Auth::loginUsingId($userId);

// Logout
Auth::logout();

Role Management

If the authorization module is installed, the User model includes the HasRoles trait and a Roles tab appears on the settings page.

HasRoles Trait Methods

<?php

$user = Auth::user();

// Check if user has a role
if ($user->hasRole('admin')) { ... }

// Check for any/all roles
$user->hasAnyRole(['admin', 'editor']);
$user->hasAllRoles(['admin', 'editor']);

// Assign a role
$role = Role::findOneBy(['name' => 'admin']);
$user->assignRole($role);
$user->save();

// Remove a role
$user->removeRole('admin');
$user->save();

// Get all role names
$roleNames = $user->getRoleNames(); // ['admin', 'editor']

// Sync roles (replace all)
$user->syncRoles([$adminRole, $editorRole]);
$user->save();

Authorization with Gates

<?php

use ZephyrPHP\Authorization\Gate;

// Define abilities
Gate::define('edit-post', function($user, $post) {
    return $user->getId() === $post->getUserId();
});

// Check abilities
if (Gate::allows('edit-post', $post)) { ... }
if (Gate::denies('edit-post', $post)) { ... }

// Authorize (throws exception if denied)
Gate::authorize('edit-post', $post);

// Using helpers
if (can('edit-post', $post)) { ... }

Protecting Routes

Authentication Middleware

The framework provides built-in middleware. No need to create your own:

<?php

use ZephyrPHP\Middleware\AuthMiddleware;
use ZephyrPHP\Middleware\GuestMiddleware;

// Protect a single route
Route::get('/profile', [ProfileController::class, 'show'])
    ->middleware([AuthMiddleware::class]);

// Protect a group
Route::group(['prefix' => '/admin', 'middleware' => [AuthMiddleware::class]], function () {
    Route::get('/users', [AdminController::class, 'users']);
    Route::get('/reports', [AdminController::class, 'reports']);
});

// Guest-only (redirect logged-in users away)
Route::get('/login', [LoginController::class, 'showForm'])
    ->middleware([GuestMiddleware::class]);

Authorization Middleware

<?php

use ZephyrPHP\Middleware\CanMiddleware;

// Check ability
Route::get('/posts/{id}/edit', [PostController::class, 'edit'])
    ->middleware([new CanMiddleware('edit-post', 'post')]);

// Rate limiting
use ZephyrPHP\Middleware\RateLimitMiddleware;

Route::post('/login', [LoginController::class, 'login'])
    ->middleware([RateLimitMiddleware::loginAttempts(5)]);

Customization

Adding Custom User Fields

Edit the generated app/Models/User.php to add fields:

<?php

// Add to User model

#[ORM\Column(type: 'string', length: 20, nullable: true)]
protected ?string $phone = null;

#[ORM\Column(type: 'boolean')]
protected bool $isActive = true;

#[ORM\Column(type: 'string', nullable: true)]
protected ?string $avatar = null;

// Add getters/setters...

Custom Login Validation

<?php

// In LoginController::login()
if (Auth::attempt($credentials)) {
    $user = Auth::user();

    // Only allow active users
    if (!$user->isActive) {
        Auth::logout();
        $this->flash('errors', ['email' => 'Your account has been deactivated.']);
        $this->back();
        return;
    }

    $this->redirect('/v1/dashboard');
}

Extending the Dashboard

Add new pages to the dashboard by creating controllers and updating the routes and sidebar layout:

// Add to routes/web.php inside the dashboard group:
Route::group(['prefix' => '/v1/dashboard', 'middleware' => [AuthMiddleware::class]], function () {
    Route::get('/', [DashboardController::class, 'index']);
    Route::get('/settings', [SettingsController::class, 'index']);
    // ... existing routes ...

    // Add your own:
    Route::get('/posts', [PostController::class, 'index']);
    Route::get('/analytics', [AnalyticsController::class, 'index']);
});

Then add navigation links in pages/layouts/dashboard.twig:

<nav class="sidebar-nav">
    <ul>
        <li><a href="/v1/dashboard">Dashboard</a></li>
        <li><a href="/v1/dashboard/posts">Posts</a></li>
        <li><a href="/v1/dashboard/analytics">Analytics</a></li>
        <li><a href="/v1/dashboard/settings">Settings</a></li>
    </ul>
</nav>

After Setup

Once auth:setup has completed:

  1. Create the database tables: php craftsman db:schema
  2. Start the dev server: php craftsman serve
  3. Visit /register to create your first account
  4. Visit /v1/dashboard to see the dashboard

Troubleshooting

Login Always Fails

# Ensure passwords are hashed with Hash::make()
$user->setPassword(Hash::make($password));

# NOT plain text:
$user->setPassword($password);

# Check database has users
SELECT * FROM users;

Session Lost After Login

# Check .env has session config
SESSION_DRIVER=file
SESSION_LIFETIME=120

# Ensure session module is enabled in config/modules.php
'session' => true,

Infinite Redirect Loop

# Login and register routes MUST use GuestMiddleware
# so authenticated users are redirected away from login page
# instead of being redirected back in a loop.

# Check routes/web.php has:
Route::get('/login', [...])->middleware([GuestMiddleware::class]);

Next Steps