Laravel 12

Laravel 12 Authentication with Sanctum: Register, Login, and Protected Pages

Introduction

Laravel 12 provides a robust authentication system. With Sanctum, API-based authentication becomes efficient. This guide helps you implement registration, login, logout, and protect routes for categories and products. Unauthenticated users will be redirected to the login page.

Prerequisites

  • Laravel 11 installed
  • Composer and PHP 8.3+
  • MySQL or any Laravel-supported database
  • Postman or a frontend (Vue.js, React, or Blade) to test API calls

Step 1: Install Laravel 12

composer create-project laravel/laravel laravel12-email-verify
cd laravel12-email-verify

Step 2: Install Sanctum

composer require laravel/sanctum

php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"


php artisan migrate

Step 3: Set Up User Authentication

Create a controller:

php artisan make:controller AuthController

Update app/Models/User.php:

use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;
}

Add this code inside AuthController.php:


<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var list<string>
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var list<string>
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * Get the attributes that should be cast.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'password' => 'hashed',
        ];
    }
}

Step 4: Protect Routes

Update routes/web.php like this:

use App\Http\Controllers\AuthController;
use App\Http\Controllers\CategoryController;
use App\Http\Controllers\ProductController;


Route::get('/login', [AuthController::class, 'showLoginForm'])->name('login');
Route::get('/register', [AuthController::class, 'showRegisterForm'])->name('register');

Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login']);

Route::middleware('auth:sanctum')->group(function () {
    Route::get('/dashboard', function () {
        return view('dashboard');
    })->name('dashboard');
    Route::post('/logout', [AuthController::class, 'logout'])->name('logout');
    Route::resource('/categories', CategoryController::class);
    Route::resource('/products', ProductController::class);
});

Step 5: Create Blade Files

Layout File

Create: resources/views/layouts/app.blade.php

<!DOCTYPE html>
<html>
<head>
    <title>@yield('title')</title>
</head>
<body>
    @yield('content')
</body>
</html>

Login Page

Create: resources/views/auth/login.blade.php

@extends('layouts.app')
@section('title', 'Login')
@section('content')
<form method="POST" action="{{ route('login') }}">
    @csrf
    <input type="email" name="email" placeholder="Email" required>
    <input type="password" name="password" placeholder="Password" required>
    <button type="submit">Login</button>
</form>
@endsection

Register Page

Create: resources/views/auth/register.blade.php

@extends('layouts.app')
@section('title', 'Register')
@section('content')
<form method="POST" action="{{ route('register') }}">
    @csrf
    <input type="text" name="name" placeholder="Name" required>
    <input type="email" name="email" placeholder="Email" required>
    <input type="password" name="password" placeholder="Password" required>
    <button type="submit">Register</button>
</form>
@endsection

Dashboard Page

Create: resources/views/dashboard.blade.php

@extends('layouts.app')
@section('title', 'Dashboard')
@section('content')
<h1>Welcome to Dashboard</h1>
<form method="POST" action="{{ route('logout') }}">
    @csrf
    <button type="submit">Logout</button>
</form>

@endsection

Categories Page

Create: resources/views/categories/index.blade.php

@extends('layouts.app')
@section('title', 'Categories')
@section('content')
<h1>Categories</h1>
<a href="{{ route('categories.create') }}">Add Category</a>
<ul>
@foreach($categories as $category)
    <li>{{ $category->name }}</li>
@endforeach
</ul>
@endsection

Products Page

Create: resources/views/products/index.blade.php

@extends('layouts.app')
@section('title', 'Products')
@section('content')
<h1>Products</h1>
<a href="{{ route('products.create') }}">Add Product</a>
<ul>
@foreach($products as $product)
    <li>{{ $product->name }}</li>
@endforeach
</ul>
@endsection

Laravel Package Discovery Summary

Package Purpose
laravel/pail Internal Laravel package for structured output (often related to logs or diagnostics).
laravel/sail Laravel Sail — the Docker-based local development environment for Laravel.
laravel/sanctum Provides token-based API authentication.
laravel/tinker Allows you to interact with your Laravel app via REPL (CLI).
nesbot/carbon Date and time manipulation (used by Laravel models like created_at, updated_at).
nunomaduro/collision Beautiful error reporting in the console (during artisan commands).
nunomaduro/termwind Renders beautiful colored and styled console output (used in Artisan).

Step 6: Enable Email Verification

To ensure only verified users can access protected routes, Laravel’s built-in email verification system can be enabled.

Update User.php to implement MustVerifyEmail

<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable implements MustVerifyEmail
{
use HasApiTokens, HasFactory, Notifiable;
protected $fillable = [
‘name’,
’email’,
‘password’,
    ];
protected $hidden = [
‘password’,
‘remember_token’,
    ];
protected function casts(): array
    {
return [
’email_verified_at’ => ‘datetime’,
‘password’ => ‘hashed’,
        ];
    }
}

 

Generate Controllers

php artisan make:controller Auth/ForgotPasswordController
php artisan make:controller Auth/ResetPasswordController

 

web.php

<?php
use Illuminate\Support\Facades\Route;
use Illuminate\Http\Request;
use Illuminate\Foundation\Auth\EmailVerificationRequest;
use App\Http\Controllers\Auth\ForgotPasswordController;
use App\Http\Controllers\Auth\ResetPasswordController;
/*
|————————————————————————–
| Web Routes
|————————————————————————–
*/
Route::get(‘/’, function () {
    return view(‘welcome’);
});
// ✅ Email Verification Routes
Route::get(‘/email/verify’, function () {
    return view(‘auth.verify’);
})->middleware(‘auth’)->name(‘verification.notice’);
Route::get(‘/email/verify/{id}/{hash}’, function (EmailVerificationRequest $request) {
    $request->fulfill(); // Marks email as verified
    return redirect(‘/dashboard’);
})->middleware([‘auth’, ‘signed’])->name(‘verification.verify’);
Route::post(‘/email/verification-notification’, function (Request $request) {
    $request->user()->sendEmailVerificationNotification();
    return back()->with(‘message’, ‘Verification link sent!’);
})->middleware([‘auth’, ‘throttle:6,1’])->name(‘verification.send’);
// ✅ Password Reset Routes
Route::get(‘/forgot-password’, [ForgotPasswordController::class, ‘showLinkRequestForm’])->name(‘password.request’);
Route::post(‘/forgot-password’, [ForgotPasswordController::class, ‘sendResetLinkEmail’])->name(‘password.email’);
Route::get(‘/reset-password/{token}’, [ResetPasswordController::class, ‘showResetForm’])->name(‘password.reset’);
Route::post(‘/reset-password’, [ResetPasswordController::class, ‘reset’])->name(‘password.update’);
// ✅ Protected Dashboard Route (requires verified email)
Route::get(‘/dashboard’, function () {
    return view(‘dashboard’);
})->middleware([‘auth’, ‘verified’])->name(‘dashboard’);

 

Create View: resources/views/auth/verify.blade.php

@extends('layouts.app')

@section('content')
    <p>Please verify your email address using the link sent to your inbox.</p>

    @if (session('message'))
        <div class=\"alert alert-success\">
            {{ session('message') }}
        </div>
    @endif

    <form method=\"POST\" action=\"{{ route('verification.send') }}\">
        @csrf
        <button type=\"submit\">Resend Verification Email</button>
    </form>
@endsection

Create View: resources/views/auth/forgot-password.blade.php

@extends('layouts.app')

@section('content')
    <h2>Forgot Password</h2>
    @if (session('status'))
        <div class=\"alert alert-success\">{{ session('status') }}</div>
    @endif

    <form method=\"POST\" action=\"{{ route('password.email') }}\">
        @csrf
        <input type=\"email\" name=\"email\" required placeholder=\"Enter your email\" />
        <button type=\"submit\">Send Reset Link</button>
    </form>
@endsection

Create View: resources/views/auth/reset-password.blade.php

@extends('layouts.app')

@section('content')
    <h2>Reset Password</h2>

    <form method=\"POST\" action=\"{{ route('password.update') }}\">
        @csrf
        <input type=\"hidden\" name=\"token\" value=\"{{ $token }}\">

        <input type=\"email\" name=\"email\" value=\"{{ old('email') }}\" required />
        <input type=\"password\" name=\"password\" required placeholder=\"New Password\" />
        <input type=\"password\" name=\"password_confirmation\" required placeholder=\"Confirm Password\" />

        <button type=\"submit\">Reset Password</button>
    </form>
@endsection

Summary

We implemented Laravel 11 authentication with Sanctum and Blade templates, including registration, login, and protected routes for dashboard, categories, and products. Unauthorized users are redirected to the login page, ensuring a secure and user-friendly system.

Leave a Reply

Your email address will not be published. Required fields are marked *