PHP & Laravel — Zero to Hero Episode 21: Authentication — User Registration, Login, and Route Protection

What Are We Doing in This Post?

Our blog works perfectly but it has a serious problem. Anyone who visits the site can create, edit, and delete any post. There is no concept of who is logged in or who owns what.

In this episode we build a complete authentication system from scratch — user registration, login, logout, session-based authentication, and route protection so only logged-in users can manage posts.

We are building this manually without any starter kit so you understand every single piece of how Laravel authentication works under the hood.


How Laravel Authentication Works

Laravel's authentication system is built on three things you already know from Core PHP.

Sessions — when a user logs in, their user ID is stored in the session. Every subsequent request, Laravel reads that session to know who the user is.

The Auth facade — a Laravel class that gives you clean methods like Auth::attempt(), Auth::login(), Auth::logout(), and Auth::user() to manage authentication state.

Middleware — the auth middleware protects routes by checking if a user is logged in before allowing access. If not logged in, the user gets redirected to the login page.


Step 1 — The User Model and Migration

Laravel already created a User model and migration when we installed it. The users table is already in your database from Episode 15.

Open app/Models/User.php and look at it — it already has $fillable with name, email, and password, and it already uses password hashing through Laravel's built-in features.

We do not need to change anything here. Laravel's User model is production-ready out of the box.


Step 2 — Create Authentication Controllers

We need two controllers — one for registration and one for login/logout.


    php artisan make:controller Auth/RegisterController
    php artisan make:controller Auth/LoginController

Laravel creates these inside app/Http/Controllers/Auth/ — a subfolder for auth-related controllers. Keeping them organized in a subfolder is standard practice.


Step 3 — Create Form Request Classes


    php artisan make:request Auth/RegisterRequest
    php artisan make:request Auth/LoginRequest

Update app/Http/Requests/Auth/RegisterRequest.php:


    <?php

    namespace App\Http\Requests\Auth;

    use Illuminate\Foundation\Http\FormRequest;

    class RegisterRequest extends FormRequest
    {
        public function authorize(): bool
        {
            return true;
        }

        public function rules(): array
        {
            return [
                'name'     => 'required|string|min:2|max:100',
                'email'    => 'required|email|unique:users,email',
                'password' => 'required|min:8|confirmed',
            ];
        }

        public function messages(): array
        {
            return [
                'name.required'      => 'Please enter your full name.',
                'name.min'           => 'Name must be at least 2 characters.',
                'email.required'     => 'Please enter your email address.',
                'email.email'        => 'Please enter a valid email address.',
                'email.unique'       => 'An account with this email already exists.',
                'password.required'  => 'Please choose a password.',
                'password.min'       => 'Password must be at least 8 characters.',
                'password.confirmed' => 'The passwords do not match.',
            ];
        }
    }

Update app/Http/Requests/Auth/LoginRequest.php:


    <?php

    namespace App\Http\Requests\Auth;

    use Illuminate\Foundation\Http\FormRequest;

    class LoginRequest extends FormRequest
    {
        public function authorize(): bool
        {
            return true;
        }

        public function rules(): array
        {
            return [
                'email'    => 'required|email',
                'password' => 'required',
            ];
        }

        public function messages(): array
        {
            return [
                'email.required'    => 'Please enter your email address.',
                'email.email'       => 'Please enter a valid email address.',
                'password.required' => 'Please enter your password.',
            ];
        }
    }


Step 4 — Build the Register Controller

Update app/Http/Controllers/Auth/RegisterController.php:


    <?php

    namespace App\Http\Controllers\Auth;

    use App\Http\Controllers\Controller;
    use App\Http\Requests\Auth\RegisterRequest;
    use App\Models\User;
    use Illuminate\Support\Facades\Auth;
    use Illuminate\Support\Facades\Hash;

    class RegisterController extends Controller
    {
        public function create()
        {
            if (Auth::check()) {
                return redirect()->route('posts.index');
            }

            return view('auth.register');
        }

        public function store(RegisterRequest $request)
        {
            $user = User::create([
                'name'     => $request->input('name'),
                'email'    => $request->input('email'),
                'password' => Hash::make($request->input('password')),
            ]);

            Auth::login($user);

            return redirect()->route('posts.index')->with('success', 'Welcome to MyBlog, ' . $user->name . '!');
        }
    }

Auth::check() returns true if a user is already logged in. We redirect them away from the register page if they are already authenticated.

Hash::make() hashes the password using bcrypt before storing it in the database. Passwords are never stored as plain text. Ever.

Auth::login($user) logs the user in immediately after registration — no need to make them log in separately right after creating their account.


Step 5 — Build the Login Controller

Update app/Http/Controllers/Auth/LoginController.php:


    <?php

    namespace App\Http\Controllers\Auth;

    use App\Http\Controllers\Controller;
    use App\Http\Requests\Auth\LoginRequest;
    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Auth;

    class LoginController extends Controller
    {
        public function create()
        {
            if (Auth::check()) {
                return redirect()->route('posts.index');
            }

            return view('auth.login');
        }

        public function store(LoginRequest $request)
        {
            $credentials = $request->only('email', 'password');
            $remember    = $request->boolean('remember');

            if (Auth::attempt($credentials, $remember)) {
                $request->session()->regenerate();
                return redirect()->intended(route('posts.index'))->with('success', 'Welcome back, ' . Auth::user()->name . '!');
            }

            return back()->withErrors([
                'email' => 'These credentials do not match our records.',
            ])->onlyInput('email');
        }

        public function destroy(Request $request)
        {
            Auth::logout();
            $request->session()->invalidate();
            $request->session()->regenerateToken();
            return redirect()->route('login')->with('success', 'You have been logged out successfully.');
        }
    }

Auth::attempt($credentials, $remember) checks the email and password against the database. It automatically hashes the provided password and compares it against the stored hash. If they match, it logs the user in and returns true. If not, it returns false.

The second argument $remember is a boolean — if true, Laravel creates a persistent "remember me" cookie so the user stays logged in even after closing the browser.

$request->session()->regenerate() generates a new session ID after login. This prevents session fixation attacks — a security vulnerability where an attacker could hijack a session.

redirect()->intended() redirects the user to wherever they were trying to go before being redirected to the login page. If they tried to visit /posts/create, got redirected to login, and then logged in — they land back on /posts/create. The second argument is the fallback URL if there was no intended destination.

withErrors() flashes specific error messages to the session. onlyInput('email') flashes only the email back to the form — never flash passwords back.

$request->session()->invalidate() and $request->session()->regenerateToken() on logout properly destroy the session and regenerate the CSRF token — a security requirement.


Step 6 — Update Routes

Update routes/web.php:


    <?php

    use Illuminate\Support\Facades\Route;
    use App\Http\Controllers\PostController;
    use App\Http\Controllers\Auth\RegisterController;
    use App\Http\Controllers\Auth\LoginController;

    Route::get('/', function () {
        return view('home');
    })->name('home');

    Route::middleware('guest')->group(function () {
        Route::get('/register', [RegisterController::class, 'create'])->name('register');
        Route::post('/register', [RegisterController::class, 'store']);
        Route::get('/login', [LoginController::class, 'create'])->name('login');
        Route::post('/login', [LoginController::class, 'store']);
    });

    Route::post('/logout', [LoginController::class, 'destroy'])->name('logout')->middleware('auth');

    Route::resource('posts', PostController::class)->only(['index', 'show']);

    Route::resource('posts', PostController::class)
        ->except(['index', 'show'])
        ->middleware('auth');

Two middleware groups used here.

middleware('guest') — the guest middleware redirects already-logged-in users away from the register and login pages. No point showing a login form to someone already logged in.

middleware('auth') — the auth middleware protects routes that require login. If an unauthenticated user tries to visit a protected route, they are redirected to the login page automatically.

We split the resource routes into two groups — index and show are public (anyone can read posts), while create, store, edit, update, and destroy require authentication.


Step 7 — Create Authentication Views

Create the folder resources/views/auth/.

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


    @extends('layouts.app')

    @section('title', 'Create Account — MyBlog')

    @section('content')

    <div style="max-width:480px; margin:0 auto;">

        <div style="text-align:center; margin-bottom:32px;">
            <h1 style="font-size:28px; font-weight:800; color:#1e293b;">Create your account</h1>
            <p style="color:#64748b; margin-top:8px;">Join MyBlog and start writing today.</p>
        </div>

        <div style="background:#fff; border:1px solid #e2e8f0; border-radius:16px; padding:36px;">

            @if($errors->any())
                <div style="background:#fef2f2; border:1px solid #fecaca; border-radius:8px; padding:16px; margin-bottom:24px;">
                    <p style="font-weight:700; color:#991b1b; margin-bottom:8px;">Please fix the following:</p>
                    <ul style="padding-left:20px; color:#b91c1c;">
                        @foreach($errors->all() as $error)
                            <li style="margin-bottom:4px; font-size:14px;">{{ $error }}</li>
                        @endforeach
                    </ul>
                </div>
            @endif

            <form method="POST" action="{{ route('register') }}">
                @csrf

                <div style="margin-bottom:20px;">
                    <label style="display:block; font-size:14px; font-weight:600; margin-bottom:6px; color:#374151;">Full Name</label>
                    <input
                        type="text"
                        name="name"
                        value="{{ old('name') }}"
                        placeholder="Your full name"
                        style="width:100%; padding:11px 14px; border:1px solid {{ $errors->has('name') ? '#ef4444' : '#e2e8f0' }}; border-radius:8px; font-size:15px; outline:none;"
                        autofocus
                    >
                    @error('name')
                        <p style="color:#ef4444; font-size:13px; margin-top:5px;">{{ $message }}</p>
                    @enderror
                </div>

                <div style="margin-bottom:20px;">
                    <label style="display:block; font-size:14px; font-weight:600; margin-bottom:6px; color:#374151;">Email Address</label>
                    <input
                        type="email"
                        name="email"
                        value="{{ old('email') }}"
                        placeholder="you@example.com"
                        style="width:100%; padding:11px 14px; border:1px solid {{ $errors->has('email') ? '#ef4444' : '#e2e8f0' }}; border-radius:8px; font-size:15px; outline:none;"
                    >
                    @error('email')
                        <p style="color:#ef4444; font-size:13px; margin-top:5px;">{{ $message }}</p>
                    @enderror
                </div>

                <div style="margin-bottom:20px;">
                    <label style="display:block; font-size:14px; font-weight:600; margin-bottom:6px; color:#374151;">Password</label>
                    <input
                        type="password"
                        name="password"
                        placeholder="Minimum 8 characters"
                        style="width:100%; padding:11px 14px; border:1px solid {{ $errors->has('password') ? '#ef4444' : '#e2e8f0' }}; border-radius:8px; font-size:15px; outline:none;"
                    >
                    @error('password')
                        <p style="color:#ef4444; font-size:13px; margin-top:5px;">{{ $message }}</p>
                    @enderror
                </div>

                <div style="margin-bottom:28px;">
                    <label style="display:block; font-size:14px; font-weight:600; margin-bottom:6px; color:#374151;">Confirm Password</label>
                    <input
                        type="password"
                        name="password_confirmation"
                        placeholder="Repeat your password"
                        style="width:100%; padding:11px 14px; border:1px solid #e2e8f0; border-radius:8px; font-size:15px; outline:none;"
                    >
                </div>

                <button type="submit" style="width:100%; background:#6366f1; color:#fff; padding:13px; border:none; border-radius:8px; font-size:15px; font-weight:700; cursor:pointer;">
                    Create Account
                </button>

            </form>

            <p style="text-align:center; margin-top:24px; font-size:14px; color:#64748b;">
                Already have an account?
                <a href="{{ route('login') }}" style="color:#6366f1; font-weight:600; text-decoration:none;">Sign in</a>
            </p>

        </div>
    </div>

    @endsection

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


    @extends('layouts.app')

    @section('title', 'Sign In — MyBlog')

    @section('content')

    <div style="max-width:480px; margin:0 auto;">

        <div style="text-align:center; margin-bottom:32px;">
            <h1 style="font-size:28px; font-weight:800; color:#1e293b;">Welcome back</h1>
            <p style="color:#64748b; margin-top:8px;">Sign in to manage your posts.</p>
        </div>

        <div style="background:#fff; border:1px solid #e2e8f0; border-radius:16px; padding:36px;">

            @if($errors->any())
                <div style="background:#fef2f2; border:1px solid #fecaca; border-radius:8px; padding:16px; margin-bottom:24px;">
                    <p style="font-weight:700; color:#991b1b; margin-bottom:8px;">Login failed:</p>
                    <ul style="padding-left:20px; color:#b91c1c;">
                        @foreach($errors->all() as $error)
                            <li style="margin-bottom:4px; font-size:14px;">{{ $error }}</li>
                        @endforeach
                    </ul>
                </div>
            @endif

            <form method="POST" action="{{ route('login') }}">
                @csrf

                <div style="margin-bottom:20px;">
                    <label style="display:block; font-size:14px; font-weight:600; margin-bottom:6px; color:#374151;">Email Address</label>
                    <input
                        type="email"
                        name="email"
                        value="{{ old('email') }}"
                        placeholder="you@example.com"
                        style="width:100%; padding:11px 14px; border:1px solid {{ $errors->has('email') ? '#ef4444' : '#e2e8f0' }}; border-radius:8px; font-size:15px; outline:none;"
                        autofocus
                    >
                    @error('email')
                        <p style="color:#ef4444; font-size:13px; margin-top:5px;">{{ $message }}</p>
                    @enderror
                </div>

                <div style="margin-bottom:20px;">
                    <label style="display:block; font-size:14px; font-weight:600; margin-bottom:6px; color:#374151;">Password</label>
                    <input
                        type="password"
                        name="password"
                        placeholder="Your password"
                        style="width:100%; padding:11px 14px; border:1px solid {{ $errors->has('password') ? '#ef4444' : '#e2e8f0' }}; border-radius:8px; font-size:15px; outline:none;"
                    >
                    @error('password')
                        <p style="color:#ef4444; font-size:13px; margin-top:5px;">{{ $message }}</p>
                    @enderror
                </div>

                <div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:28px;">
                    <label style="display:flex; align-items:center; gap:8px; font-size:14px; color:#374151; cursor:pointer;">
                        <input type="checkbox" name="remember" style="width:16px; height:16px;">
                        Remember me
                    </label>
                </div>

                <button type="submit" style="width:100%; background:#6366f1; color:#fff; padding:13px; border:none; border-radius:8px; font-size:15px; font-weight:700; cursor:pointer;">
                    Sign In
                </button>

            </form>

            <p style="text-align:center; margin-top:24px; font-size:14px; color:#64748b;">
                Do not have an account?
                <a href="{{ route('register') }}" style="color:#6366f1; font-weight:600; text-decoration:none;">Create one</a>
            </p>

        </div>
    </div>

    @endsection


Step 8 — Update the Navigation Layout

Update resources/views/layouts/app.blade.php to show the correct navigation based on authentication state:


    <!DOCTYPE html>
    <html lang="en">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>@yield('title', 'MyBlog')</title>
        <style>
            * {
                box-sizing: border-box;
                margin: 0;
                padding: 0;
            }

            body {
                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
                background: #f8fafc;
                color: #1e293b;
            }

            nav {
                background: #1e293b;
                padding: 16px 32px;
                display: flex;
                justify-content: space-between;
                align-items: center;
            }

            nav .brand {
                color: #fff;
                font-size: 20px;
                font-weight: 700;
                text-decoration: none;
            }

            nav ul {
                list-style: none;
                display: flex;
                gap: 24px;
                align-items: center;
            }

            nav ul a {
                color: #94a3b8;
                text-decoration: none;
                font-size: 14px;
                font-weight: 500;
            }

            nav ul a:hover {
                color: #fff;
            }

            .nav-btn {
                background: #6366f1;
                color: #fff !important;
                padding: 7px 16px;
                border-radius: 6px;
                font-size: 13px !important;
            }

            .nav-btn:hover {
                opacity: 0.88;
            }

            .container {
                max-width: 860px;
                margin: 40px auto;
                padding: 0 20px;
            }

            footer {
                text-align: center;
                padding: 32px;
                color: #64748b;
                font-size: 13px;
                border-top: 1px solid #e2e8f0;
                margin-top: 60px;
            }

            .alert-success {
                background: #f0fdf4;
                border: 1px solid #86efac;
                padding: 12px 16px;
                border-radius: 8px;
                margin-bottom: 24px;
                color: #166534;
                font-size: 14px;
            }
        </style>
        @stack('styles')
    </head>

    <body>

        <nav>
            <a href="{{ route('home') }}" class="brand">MyBlog</a>
            <ul>
                <li><a href="{{ route('posts.index') }}">Posts</a></li>

                @auth
                <li><a href="{{ route('posts.create') }}">Write Post</a></li>
                <li>
                    <span style="color:#64748b; font-size:13px;">{{ Auth::user()->name }}</span>
                </li>
                <li>
                    <form method="POST" action="{{ route('logout') }}" style="display:inline;">
                        @csrf
                        <button type="submit"
                            style="background:none; border:none; color:#94a3b8; font-size:14px; font-weight:500; cursor:pointer; padding:0;">
                            Logout
                        </button>
                    </form>
                </li>
                @else
                <li><a href="{{ route('login') }}">Sign In</a></li>
                <li><a href="{{ route('register') }}" class="nav-btn">Get Started</a></li>
                @endauth
            </ul>
        </nav>

        <div class="container">

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

            @yield('content')

        </div>

        <footer>
            &copy; {{ date('Y') }} MyBlog. Built with Laravel.
        </footer>

        @stack('scripts')
    </body>

    </html>

@auth ... @else ... @endauth is a Blade directive that renders its content only for authenticated users. The @else block renders for guests. This is the clean way to show different navigation to logged-in vs logged-out users.

Auth::user() returns the currently authenticated User model. Auth::user()->name gives you their name.


Step 9 — Protect Posts With Ownership

Right now any logged-in user can edit or delete any post. Let us add ownership — users can only manage their own posts.

First update the posts migration to make user_id required. Run:


    php artisan migrate:fresh --seed

Update PostController to associate posts with the logged-in user and enforce ownership:


    <?php

    namespace App\Http\Controllers;

    use App\Http\Requests\StorePostRequest;
    use App\Http\Requests\UpdatePostRequest;
    use App\Models\Post;
    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Auth;
    use Illuminate\Support\Str;

    class PostController extends Controller
    {
        public function index()
        {
            $posts = Post::latest()->get();
            return view('posts.index', ['posts' => $posts]);
        }

        public function create()
        {
            return view('posts.create');
        }

        public function store(StorePostRequest $request)
        {
            Post::create([
                'user_id'      => Auth::id(),
                'title'        => $request->input('title'),
                'slug'         => Str::slug($request->input('title')),
                'body'         => $request->input('body'),
                'status'       => $request->input('status'),
                'published_at' => $request->input('status') === 'published' ? now() : null,
            ]);

            return redirect()->route('posts.index')->with('success', 'Post created successfully!');
        }

        public function show($id)
        {
            $post = Post::findOrFail($id);
            $post->increment('views');
            return view('posts.show', ['post' => $post]);
        }

        public function edit($id)
        {
            $post = Post::findOrFail($id);

            if ($post->user_id !== Auth::id()) {
                abort(403, 'You do not have permission to edit this post.');
            }

            return view('posts.edit', ['post' => $post]);
        }

        public function update(UpdatePostRequest $request, $id)
        {
            $post = Post::findOrFail($id);

            if ($post->user_id !== Auth::id()) {
                abort(403, 'You do not have permission to update this post.');
            }

            $post->update([
                'title' => $request->input('title'),
                'slug'  => Str::slug($request->input('title')),
                'body'  => $request->input('body'),
            ]);

            return redirect()->route('posts.show', $post->id)->with('success', 'Post updated successfully!');
        }

        public function destroy($id)
        {
            $post = Post::findOrFail($id);

            if ($post->user_id !== Auth::id()) {
                abort(403, 'You do not have permission to delete this post.');
            }

            $post->delete();
            return redirect()->route('posts.index')->with('success', 'Post deleted successfully!');
        }
    }

Auth::id() returns the ID of the currently logged-in user — a shortcut for Auth::user()->id.

abort(403) returns a Forbidden response. Laravel has a beautiful default 403 error page. 403 means the user is authenticated but not authorized to perform this action.


Update the Show View to Conditionally Show Edit/Delete

Update resources/views/posts/show.blade.php — only show edit and delete buttons to the post owner:


    @extends('layouts.app')

    @section('title', $post->title . ' — MyBlog')

    @section('content')

    <article
        style="background:#fff; border:1px solid #e2e8f0; border-radius:12px; padding:40px; max-width:700px; margin:0 auto;">

        <span
            style="display:inline-block; padding:3px 10px; border-radius:20px; font-size:11px; font-weight:600; background:#f0fdf4; color:#166534; margin-bottom:16px;">
            {{ ucfirst($post->status) }}
        </span>

        <h1 style="font-size:30px; font-weight:800; margin-bottom:8px; color:#1e293b;">{{ $post->title }}</h1>

        <div
            style="display:flex; gap:16px; color:#94a3b8; font-size:13px; margin-bottom:32px; padding-bottom:24px; border-bottom:1px solid #e2e8f0;">
            <span>{{ $post->created_at->format('d M Y') }}</span>
            <span>{{ $post->views }} views</span>
        </div>

        <div style="font-size:16px; line-height:1.85; color:#334155;">
            {{ $post->body }}
        </div>

        <div
            style="margin-top:40px; padding-top:24px; border-top:1px solid #e2e8f0; display:flex; gap:16px; align-items:center;">
            <a href="{{ route('posts.index') }}" style="color:#6366f1; font-weight:600; text-decoration:none;">← All
                Posts</a>

            @auth
            @if(Auth::id() === $post->user_id)
            <a href="{{ route('posts.edit', $post->id) }}"
                style="color:#f59e0b; font-weight:600; text-decoration:none;">Edit</a>

            <form method="POST" action="{{ route('posts.destroy', $post->id) }}" style="display:inline;"
                onsubmit="return confirm('Delete this post?')">
                @csrf
                @method('DELETE')
                <button type="submit"
                    style="background:none; border:none; color:#ef4444; font-weight:600; cursor:pointer; font-size:16px; padding:0;">Delete</button>
            </form>
            @endif
            @endauth
        </div>

    </article>

    @endsection


Testing the Complete Auth Flow

Start the server:

php artisan serve

Visit http://127.0.0.1:8000/register — register a new account. You are logged in automatically and redirected to posts.

The navigation now shows your name, a Write Post link, and a Logout button.

Visit http://127.0.0.1:8000/posts/create while logged out — you get redirected to login automatically. That is the auth middleware working.

Login and create a post. Go to the post — you see Edit and Delete buttons because you are the owner.

Open a different browser or incognito window — visit the same post as a guest. No Edit or Delete buttons visible.

Try visiting /posts/1/edit directly as a different user — you get a 403 Forbidden error.

This is complete authentication and authorization working together.


What Did We Learn in This Post?

Laravel authentication is built on sessions, the Auth facade, and middleware. No magic — the same concepts from Core PHP, just implemented cleanly by the framework.

Auth::attempt() verifies credentials and logs the user in. Auth::login() logs a user in directly. Auth::logout() destroys the session. Auth::check() returns true if logged in. Auth::user() returns the logged-in User model. Auth::id() returns their ID.

Hash::make() bcrypt-hashes passwords before storing. Passwords are never stored as plain text.

session()->regenerate() after login prevents session fixation attacks. session()->invalidate() and session()->regenerateToken() after logout properly destroy the session.

middleware('auth') protects routes — unauthenticated users get redirected to login. middleware('guest') protects auth pages — authenticated users get redirected away.

redirect()->intended() sends users back to where they were trying to go before being redirected to login.

@auth ... @else ... @endauth in Blade renders different content for authenticated vs guest users.

abort(403) returns a Forbidden response when a user is authenticated but not authorized for an action.


What is Coming in Episode 22?

Our blog now has users and posts. But there is no relationship defined between them in Eloquent. Episode 22 covers Eloquent Relationships — belongsTo, hasMany, hasOne, and belongsToMany. We define the User-Post relationship properly and use it to display the author's name on each post, fetch all posts by a specific user, and understand how Laravel handles related data elegantly.

See you in the next one.


Next Episode: Eloquent Relationships — Connecting Models Together

This is Episode 21 of the PHP and Laravel — Zero to Hero series.


No comments:

Post a Comment

PHP & Laravel — Zero to Hero Episode 21: Authentication — User Registration, Login, and Route Protection

What Are We Doing in This Post? Our blog works perfectly but it has a serious problem. Anyone who visits the site can create, edit, and del...