Authentication System

Rhapsody provides a complete, self-contained authentication system built with Doctrine ORM and the User entity. It handles registration, login, logout, and password reset flows out of the box.

Important:

The authentication system is optional. You are not required to use it. You can build your own authentication system, or extend the built-in one.


Installation

Before using the authentication system, you must create the required database tables. Run the following command from your project root:

php rhapsody auth:install

This command creates the users and password_resets tables based on the core User entity definition.

⚠️ Important:

Running auth:install is required before any authentication routes will work. The command will skip tables that already exist, so it's safe to run multiple times.


How It Works

The authentication system is self-contained within the core framework:

  • Controller: Rhapsody\Core\Controllers\AuthController
  • Entity: Rhapsody\Core\Entities\User
  • Views: @core/auth/*.twig (login, register, forgot-password, reset-password)
  • Routes: Registered automatically from the core routes file

The following routes are available out of the box:

GET  /login              → Show login form
POST /login              → Handle login
GET  /register           → Show registration form
POST /register           → Handle registration
POST /logout             → Log out
GET  /forgot-password    → Show forgot password form
POST /forgot-password    → Send reset link
GET  /reset-password/{token} → Show reset password form
POST /reset-password     → Handle password reset

Using the Built-in Auth System

To use the default authentication system, simply ensure the routes are registered (they are automatically loaded from the core) and point your users to /login and /register.

In your templates, you can check if a user is logged in using the auth global:

{{% if auth.check %}}
    Welcome back, {{ auth.user.name }}!
    <a href="{{ base_url }}/logout">Logout</a>
{{% else %}}
    <a href="{{ base_url }}/login">Login</a>
    <a href="{{ base_url }}/register">Register</a>
{{% endif %}}

Protect routes with the auth middleware:

Router::get('/dashboard', [DashboardController::class, 'index'])->middleware('auth');

Extending the Built-in Auth System

If you want to add custom functionality (e.g., additional fields, custom validation, or custom email templates), you can extend the core controller:

// app/Controllers/AuthController.php
namespace App\Controllers;

use Rhapsody\Core\Controllers\AuthController as BaseAuthController;
use Rhapsody\Core\Request;
use Rhapsody\Core\Response;

class AuthController extends BaseAuthController
{
    public function showRegisterForm(): Response
    {
        // Add custom data to the registration view
        return $this->view('auth/register.twig', [
            'terms_accepted' => false,
        ]);
    }

    public function register(Request $request): Response
    {
        // Add custom validation before parent registration
        // Or override the entire registration logic
        // return parent::register($request);
    }
}

Then update your routes/web.php to point to your custom controller:

Router::get('/register', [App\Controllers\AuthController::class, 'showRegisterForm']);
Router::post('/register', [App\Controllers\AuthController::class, 'register']);

Extending the User Entity

To add custom fields to the user (e.g., phone, avatar), you can extend the core entity:

// app/Entities/User.php
namespace App\Entities;

use Doctrine\ORM\Mapping as ORM;
use Rhapsody\Core\Entities\User as BaseUser;

#[ORM\Entity]
#[ORM\Table(name: 'users')]
class User extends BaseUser
{
    #[ORM\Column(type: 'string', nullable: true)]
    private ?string $phone = null;

    public function getPhone(): ?string
    {
        return $this->phone;
    }

    public function setPhone(?string $phone): void
    {
        $this->phone = $phone;
    }
}

Note: If you create your own App\Entities\User entity, it will override the core User entity. Make sure your entity contains all the fields the auth system expects, or extend the base entity as shown above.


Building Your Own Auth System (Alternative)

If the built-in authentication system doesn't meet your needs, you can build your own from scratch. This is recommended if you need:

  • Integration with external authentication providers (LDAP, OAuth)
  • Non-email based login (e.g., username only)
  • Custom session management
  • Different user storage (e.g., Redis, external API)

To build your own system, you can either:

  1. Create a completely separate auth controller – Create your own LoginController and RegisterController in app/Controllers/ and use your own routes.
  2. Create independent login/register controllers – This is the recommended approach as it keeps responsibilities separated.
// app/Controllers/LoginController.php
namespace App\Controllers;

use Rhapsody\Core\BaseController;
use Rhapsody\Core\Request;
use Rhapsody\Core\Response;
use Rhapsody\Core\Session;

class LoginController extends BaseController
{
    public function showForm(): Response
    {
        return $this->view('auth/custom-login.twig');
    }

    public function login(Request $request): Response
    {
        // Your custom login logic here
        // Validate credentials, set session, etc.
    }

    public function logout(): Response
    {
        Session::destroy();
        return redirect('/login');
    }
}

Then register your routes in routes/web.php:

Router::get('/login', [LoginController::class, 'showForm']);
Router::post('/login', [LoginController::class, 'login']);
Router::post('/logout', [LoginController::class, 'logout']);

Customizing Views

To override the default authentication views, create matching templates in your active theme:

views/themes/your-theme/auth/login.twig
views/themes/your-theme/auth/register.twig
views/themes/your-theme/auth/forgot-password.twig
views/themes/your-theme/auth/reset-password.twig

The framework will automatically use your theme's templates instead of the core ones if you have set up your own theme; otherwise make sure that your view files extend @core/layouts/main.twig. See the themes documentation for more details on using core and custom themes.


Security Features

The built-in authentication system includes the following security measures:

  • Password hashing: Passwords are hashed using password_hash() with BCRYPT
  • CSRF protection: All forms include a CSRF token field
  • Session regeneration: Session ID is regenerated on login
  • Password reset tokens: Tokens are hashed at rest (SHA-256) and time-limited (60 minutes)
  • No user enumeration: Password reset responses are identical whether or not the email exists
  • Single-use tokens: Reset tokens are deleted after successful use
  • Old token invalidation: Requesting a new reset link invalidates all previous tokens

Need Help?

Check the FAQ or open an issue on GitHub.