Next video in… 10
Getting Started with Laravel
We're finally going to make use of the Sign In and Sign Up buttons! Well, first the Sign Up button in this lesson.
We've been pretending that we can edit or delete any chirp that's added, and right now, anytime we add a new one, it's just an anonymous person creating this chirp. Time to change that!
Laravel offers complete authentication scaffolding with Laravel Starter Kits, but it's also helpful to know how to manually build it first to understand how things work under the hood. So let's start with user registration and learn what's happening behind the scenes.
Let's start by creating a new page. We have our chirps directory where we have our edit page, so it makes sense to create a new directory that is an auth directory. So let's create auth
and then we'll have register.blade.php
.
Create a new file at resources/views/auth/register.blade.php
:
1<x-layout> 2 <x-slot:title> 3 Register 4 </x-slot:title> 5 6 <div class="hero min-h-[calc(100vh-16rem)]"> 7 <div class="hero-content flex-col"> 8 <div class="card w-96 bg-base-100"> 9 <div class="card-body">10 <h1 class="text-3xl font-bold text-center mb-6">Create Account</h1>11 12 <form method="POST" action="/register">13 @csrf14 15 <!-- Name -->16 <label class="floating-label mb-6">17 <input type="text"18 name="name"19 placeholder="John Doe"20 value="{{ old('name') }}"21 class="input input-bordered @error('name') input-error @enderror"22 required>23 <span>Name</span>24 </label>25 @error('name')26 <div class="label -mt-4 mb-2">27 <span class="label-text-alt text-error">{{ $message }}</span>28 </div>29 @enderror30 31 <!-- Email -->32 <label class="floating-label mb-6">33 <input type="email"34 name="email"36 value="{{ old('email') }}"37 class="input input-bordered @error('email') input-error @enderror"38 required>39 <span>Email</span>40 </label>41 @error('email')42 <div class="label -mt-4 mb-2">43 <span class="label-text-alt text-error">{{ $message }}</span>44 </div>45 @enderror46 47 <!-- Password -->48 <label class="floating-label mb-6">49 <input type="password"50 name="password"51 placeholder="••••••••"52 class="input input-bordered @error('password') input-error @enderror"53 required>54 <span>Password</span>55 </label>56 @error('password')57 <div class="label -mt-4 mb-2">58 <span class="label-text-alt text-error">{{ $message }}</span>59 </div>60 @enderror61 62 <!-- Password Confirmation -->63 <label class="floating-label mb-6">64 <input type="password"65 name="password_confirmation"66 placeholder="••••••••"67 class="input input-bordered"68 required>69 <span>Confirm Password</span>70 </label>71 72 <!-- Submit Button -->73 <div class="form-control mt-8">74 <button type="submit" class="btn btn-primary btn-sm w-full">75 Register76 </button>77 </div>78 </form>79 80 <div class="divider">OR</div>81 <p class="text-center text-sm">82 Already have an account?83 <a href="/login" class="link link-primary">Sign in</a>84 </p>85 </div>86 </div>87 </div>88 </div>89</x-layout>
The majority of things we're seeing here are not new. We've seen them throughout our time together. We have a slot where we're saying this is the register page. We're using a layout. We're using a bunch of different forms. We're gathering any errors that we might have. We're showing those error messages, and then we're using old inputs if we have any errors. So again, not much new here!
We could create a new authentication controller, but what we're going to do is kind of split this up into different controllers. We're going to go against the grain just a little bit here.
Laravel recommends using single action controllers (invokable controllers) for actions that don't fit the standard resource pattern. Let's create a dedicated controller for registration:
1php artisan make:controller Auth/Register --invokable
This creates a single action controller in the auth namespace: /Http/Controllers/Auth/Register.php
. What this looks like is a single class, an invokable controller, which just means that usually you only have one method per controller. Instead of having a resource controller that has multiple (seven different) methods.
This is really personal preference, but I wanted to show two different ways that you might do it. Everything that we're doing within auth, we're going to have invokable controllers instead of one massive authentication controller that has weird method names.
This creates a single action controller in app/Http/Controllers/Auth/Register.php
:
1<?php 2 3namespace App\Http\Controllers\Auth; 4 5use App\Http\Controllers\Controller; 6use App\Models\User; 7use Illuminate\Http\Request; 8use Illuminate\Support\Facades\Auth; 9use Illuminate\Support\Facades\Hash;10 11class Register extends Controller12{13 public function __invoke(Request $request)14 {15 // Validate the input16 $validated = $request->validate([17 'name' => 'required|string|max:255',18 'email' => 'required|string|email|max:255|unique:users',19 'password' => 'required|string|min:8|confirmed',20 ]);21 22 // Create the user23 $user = User::create([24 'name' => $validated['name'],25 'email' => $validated['email'],26 'password' => Hash::make($validated['password']),27 ]);28 29 // Log them in30 Auth::login($user);31 32 // Redirect to home33 return redirect('/')->with('success', 'Welcome to Chirper!');34 }35}
We just pretend that this __invoke
is whatever method we normally would use in our ChirpController, for example. But this is just our register auth controller, and when we call it, it's going to use this invokable function.
Here's what's happening (almost identical to any other method or form that we've done):
Validate the input - We get the name, email, and password. We're making sure the password is confirmed (that's what the password_confirmation
field is for).
Create the user - With a hash of that particular password.
Log them in - Using the Auth facade helper method to log in the user we just created.
Redirect - To home with a success status.
It looks like this shouldn't be as easy as it is, right? But it is mostly because of the helper methods that Laravel provides—not just the authentication helper method to log in the user, but also things like Hash to make passwords secure. And because we already have a User model given to us out of the box within Laravel, this becomes incredibly easy to get authentication up and running.
Let's add this to our web routes. In routes/web.php
, add the registration routes:
1use App\Http\Controllers\Auth\Register;2 3// Registration routes4Route::view('/register', 'auth.register')5 ->middleware('guest')6 ->name('register');7 8Route::post('/register', Register::class)9 ->middleware('guest');
There's something I want to add right now. I want to make sure that these routes should be able to be accessed by anyone, even if they're not logged in. This is where middleware comes into play, and Laravel has middleware built out of the box for us. In this case, for the register route, I want to make sure that this is accessible by guests.
I also want to show what a named route looks like. We can pass in a name, and I'm going to call this the register route. We can do the same thing with our POST method as well.
Note: Since we're just showing a view for the GET route, we use Route::view()
directly. For the POST route, we pass the invokable controller class.
The guest
middleware ensures only non-authenticated users can access these routes. This name helps us—instead of knowing that it is the /register
page, maybe we change this to /new
for some reason. Well, then we automatically know that it's still under the named route of register.
We can actually update the navigation instead of that href="/register"
to use a named route. But I also want to update this because when we're logged in, we shouldn't show the Sign In and Sign Up page. That's where the @auth
directive comes in.
Let's update our layout to show the current user. In resources/views/components/layout.blade.php
, update the navbar-end section:
1<div class="navbar-end gap-2"> 2 @auth 3 <span class="text-sm">{{ auth()->user()->name }}</span> 4 <form method="POST" action="/logout" class="inline"> 5 @csrf 6 <button type="submit" class="btn btn-ghost btn-sm">Logout</button> 7 </form> 8 @else 9 <a href="/login" class="btn btn-ghost btn-sm">Sign In</a>10 <a href="{{ route('register') }}" class="btn btn-primary btn-sm">Sign Up</a>11 @endauth12</div>
Instead of the Sign In and Sign Up buttons, we can say: if someone is authenticated into the application, then we're going to show the authenticated user's name and a logout button (we'll set this up in a bit). Otherwise, let's show the buttons like we had them previously.
So just so we aren't stuck without being able to test, let's go ahead and protect the rest of our routes and update our controllers to use our user now. We can actually group routes with middleware.
Now let's make sure only authenticated users can create chirps. Update your routes:
1Route::get('/', [ChirpController::class, 'index']);2 3// Protected routes4Route::middleware('auth')->group(function () {5 Route::post('/chirps', [ChirpController::class, 'store']);6 Route::get('/chirps/{chirp}/edit', [ChirpController::class, 'edit']);7 Route::put('/chirps/{chirp}', [ChirpController::class, 'update']);8 Route::delete('/chirps/{chirp}', [ChirpController::class, 'destroy']);9});
Now everything that we see here is under the auth
middleware. So I can't send a new chirp now unless I am authenticated. What does this look like? Well, if I try to send a chirp and I'm not logged in, Laravel gives you something out of the box that—because it knows you're not authenticated—it's going to try to authenticate you by redirecting to the login route.
Let's go ahead and update our ChirpController because now we're not just creating a new chirp—we're actually creating a new chirp with our users. Now we can use the real authenticated user! Update your controller methods:
1public function store(Request $request) 2{ 3 $validated = $request->validate([ 4 'message' => 'required|string|max:255', 5 ]); 6 7 // Use the authenticated user 8 auth()->user()->chirps()->create($validated); 9 10 return redirect('/')->with('success', 'Your chirp has been posted!');11}12 13public function edit(Chirp $chirp)14{15 $this->authorize('update', $chirp);16 17 return view('chirps.edit', compact('chirp'));18}19 20public function update(Request $request, Chirp $chirp)21{22 $this->authorize('update', $chirp);23 24 $validated = $request->validate([25 'message' => 'required|string|max:255',26 ]);27 28 $chirp->update($validated);29 30 return redirect('/')->with('success', 'Chirp updated!');31}32 33public function destroy(Chirp $chirp)34{35 $this->authorize('delete', $chirp);36 37 $chirp->delete();38 39 return redirect('/')->with('success', 'Chirp deleted!');40}
We're using the auth()
helper and we can say that the user of this auth helper, we want to get their chirps, but we want to create them. We can actually create them with that validated parameter.
Since we set up that policy to authorize a user to update a particular chirp, we can add that here. This $this->authorize()
method will check if they are authorized to update the chirp, then let's continue. We'll need the controller to have the AuthorizesRequests
trait that it can use.
Now we still see these edit and delete buttons when we're not logged in, but we've started to authorize if someone is able to edit or delete. How can we only show these edit and delete buttons when we are able to edit and delete them?
Instead of checking if the auth check exists and if the auth ID is equal to the chirp user ID, there's an easier way to do this. We just need to check that policy that we created. Can they update or can they delete a chirp?
Update the chirp component to use proper authentication. In resources/views/components/chirp.blade.php
, replace the temporary auth check:
1@can('update', $chirp)2 <div class="flex gap-1">3 <a href="/chirps/{{ $chirp->id }}/edit" class="btn btn-ghost btn-xs"> Edit </a>4 <form method="POST" action="/chirps/{{ $chirp->id }}"> @csrf @method('DELETE') <button5 type="submit" onclick="return confirm('Are you sure you want to delete this chirp?')"6 class="btn btn-ghost btn-xs text-error"> Delete </button>7 </form>8 </div>9@endcan
We can use the Blade directive @can
to check if they can update the chirp (in this case, the chirp that we're passing in), and then the @endcan
directive. In this case, if I can't edit any of these chirps and I can't delete any of them because I'm not logged in, the buttons won't show. But if I sign up and create a new account, after I create a chirp I can actually edit and delete them.
While the chirp form is visible to everyone on the home page, the POST route to /chirps
is protected by the auth
middleware. This means:
Guests can see the form but will be redirected to login if they try to submit
Only authenticated users can successfully post chirps
The middleware protection ensures security even if the form is visible
This approach lets visitors see what they could do if they sign up, encouraging registration.
Behind the scenes, Laravel:
Hashes passwords using bcrypt (never store plain text!)
Creates a session when users log in
Sets a cookie to remember the session
Provides the auth()
helper to access the current user
Offers middleware to protect routes
Let's test this out!
Visit /register
Fill out the form with your name, email, and password
Submit it
You should be logged in and redirected to the home page with a "Welcome to Chirper!" message
Try creating a chirp—it now belongs to you and you can see your avatar!
You now have your user avatar associated with you because you're logged in, and you can see the edit and delete methods available for your own chirps.
Laravel makes email verification easy too! You'd just:
Implement MustVerifyEmail
on your User model
Add the verified
middleware to protected routes
Laravel handles sending verification emails
We'll skip this for now, but it's there when you need it!
Hopefully this shows you that authentication doesn't have to be too scary. Authentication within Laravel is actually incredibly easy because of the tools that it provides. While yes, it's great that you can just grab a starter kit from Laravel and you have authentication out of the box, you don't necessarily need to use it every single time.
That being said, starter kits are great because they have a lot that we're not going to even touch within Chirper, such as password reset pages.
But registration works great, and users need to log back in! In the next lesson, we'll add login and logout functionality to complete our authentication system. Almost there!
Laravel is the most productive way to
build, deploy, and monitor software.