Setting up a basic user authentication system—including registration, login, sessions, password resets, and access permissions—can often be one of the more time-consuming pieces of creating the foundation of an application. It’s a prime candidate for extracting functionality out to a library, and there are quite a few such libraries.
But because of how much authentication needs vary across projects, most authentication systems grow bulky and unusable quickly. Thankfully, Laravel has found a way to make an authentication system that’s easy to use and understand, but flexible enough to fit in a variety of settings.
Every new install of Laravel has a create_users_table migration and a User model built in out of the box. Laravel offers an Artisan make:auth command that seeds a collection of authentication-related views and routes. And every install comes with a RegisterController, a LoginController, a ForgotPasswordController, and a ResetPasswordController. The APIs are clean and clear, and the conventions all work together to provide a simple—and seamless—authentication and authorization system.
Note that in Laravel 5.1 and 5.2, most of this functionality lived in the AuthController; in 5.3 and higher, this functionality has been split out into multiple controllers. Many of the specifics we’ll cover here about how to customize redirect routes, auth guards, and such are different in 5.1 and 5.2 (though all the core functionality is the same). So, if you’re on 5.1 or 5.2 and want to change some of the default authentication behaviors, you’ll likely need to dig a bit into your AuthController to see how exactly you should customize it.
When you create a new Laravel application, the first migration and model you’ll see are the create_users_table migration and the App\User model. Example 9-1 shows, straight from the migration, the fields you’ll get in your users table.
Schema::create('users',function(Blueprint$table){$table->increments('id');$table->string('name');$table->string('email')->unique();$table->string('password');$table->rememberToken();$table->timestamps();});
We have an autoincrementing primary key ID, a name, a unique email, a password, a “remember me” token, and created and modified timestamps. This covers everything you need to handle basic user authentication in most apps.
Authentication means verifying who someone is, and allowing them to act as that person in your system. This includes the login and logout processes, and any tools that allow the users to identify themselves during their time using the application.
Authorization means determining whether the authenticated user is allowed (authorized) to perform a specific behavior. For example, an authorization system allows us to forbid any non-administrators from viewing the site’s earnings.
The User model is a bit more complex, as you can see in Example 9-2. The App\User class itself is simple, but it extends the Illuminate\Foundation\Auth\User class, which pulls in several traits.
<?php// App\UsernamespaceApp;useIlluminate\Notifications\Notifiable;useIlluminate\Foundation\Auth\UserasAuthenticatable;classUserextendsAuthenticatable{useNotifiable;/*** The attributes that are mass assignable.** @var array*/protected$fillable=['name','email','password',];/*** The attributes that should be hidden for arrays.** @var array*/protected$hidden=['password','remember_token',];}
<?php// Illuminate\Foundation\Auth\UsernamespaceIlluminate\Foundation\Auth;useIlluminate\Auth\Authenticatable;useIlluminate\Database\Eloquent\Model;useIlluminate\Auth\Passwords\CanResetPassword;useIlluminate\Foundation\Auth\Access\Authorizable;useIlluminate\Contracts\Auth\AuthenticatableasAuthenticatableContract;useIlluminate\Contracts\Auth\Access\AuthorizableasAuthorizableContract;useIlluminate\Contracts\Auth\CanResetPasswordasCanResetPasswordContract;classUserextendsModelimplementsAuthenticatableContract,AuthorizableContract,CanResetPasswordContract{useAuthenticatable,Authorizable,CanResetPassword;}
If this is entirely unfamiliar, consider reading Chapter 5 before continuing to learn how Eloquent models work.
So, what can we learn from this model? First, users live in the users table; Laravel will infer this from the class name. We are able to fill out the name, email, and password properties when creating a new user, and the password and remember_token properties are excluded when outputting the user as JSON. Looking good so far.
We also can see from the contracts and the traits in the Illuminate\Foundation\Auth verison of User that there are some features in the framework (the ability to authenticate, to authorize, and to reset passwords) that theoretically could be applied to other models, not just the User model, and that could be applied individually or together.
The Authenticatable contract requires methods (getAuthIdentifier(), etc.) that allow the framework to authenticate instances of this model to the auth system; the Authenticatable trait includes the methods necessary to satisfy that contract with an average Eloquent model.
The Authorizable contract requires a method (can()) that allows the framework to authorize instances of this model for their access permissions in different contexts. Unsurprisingly, the Authorizable trait provides methods that will satisfy the Authorizable contract for an average Eloquent model.
And finally, the CanResetPassword contract requires methods (getEmailForPasswordReset(), sendPasswordResetNotification()) that allow the framework to, you guessed it, reset the password of any entity that satisfies this contract. The CanResetPassword trait provides methods to satisfy that contract for an average Eloquent model.
At this point, we have the ability to easily represent an individual user in the database (with the migration), and to pull them out with a model instance that can be authenticated (logged in and out), authorized (checked for access permissions to a particular resource), and sent a password reset email.
The auth() global helper is the easiest way to interact with the status of the authenticated user throughout your app. You can also inject an instance of Illuminate\Auth\AuthManager and get the same functionality, or use the Auth facade.
The most common usages are to check whether a user is logged in (auth()->check() returns true if the current user is logged in; auth()->guest() returns true if the user is not logged in) and to get the currently logged-in user (use auth()->user(), or auth()->id() for just the ID; both return null if no user is logged in).
Take a look at Example 9-3 for a sample usage of the global helper in a controller.
publicfunctiondashboard(){if(auth()->guest()){returnredirect('sign-up');}returnview('dashboard')->with('user',auth()->user());}
The next section is going to go into a little bit of depth of how the auth system works behind the scenes. It’s useful information but not vital, so if you want to skip it now, check out the next section, “The Auth Scaffold”.
So, how do we actually log users in? And how do we trigger those password resets?
It all happens in the Auth-namespaced controllers: RegisterController, LoginController, ResetPasswordController, and ForgotPasswordController.
The RegisterController, in combination with the RegistersUsers trait, contains sensible defaults for how to show new users a registration form, how to validate their input, how to create new users once their input is validated, and where to redirect them afterward.
The controller itself just contains a few hooks that the traits will call at given points. That makes it easy to customize a few common behaviors without having to dig deeply into the code that makes it all work.
The $redirectTo property defines where users will be redirected after registration. The validator() method defines how to validate registrations. And the create() method defines how to create a new user based on an incoming registration. Take a look at Example 9-4 to see the default RegisterController.
...classRegisterControllerextendsController{useRegistersUsers;protected$redirectTo='/home';...protectedfunctionvalidator(array$data){returnValidator::make($data,['name'=>'required|string|max:255','email'=>'required|string|email|max:255|unique:users','password'=>'required|string|min:6|confirmed',]);}protectedfunctioncreate(array$data){returnUser::create(['name'=>$data['name'],'email'=>$data['email'],'password'=>Hash::make($data['password']),]);}}
The RegistersUsers trait, which the RegisterController imports, handles a few primary functions for the registration process. First, it shows users the registration form view, with the showRegistrationForm() method. If you want new users to register with a view other than auth.register you can override the showRegistrationForm() method in your RegisterController.
Next, it handles the POST of the registration form with the register() method. This method passes the user’s registration input to the validator from the validator() method of your RegisterController, and then on to the create() method.
And finally, the redirectPath() method (pulled in via the RedirectsUsers trait) defines where users should be redirected after a successful registration. You can define this URI with the redirectTo property on your controller, or you can override the redirectPath() method and return whatever you want.
If you want this trait to use a different auth guard than the default (you’ll learn more about guards in “Guards”), you can override the guard() method and have it return whichever guard you’d like.
The LoginController, unsurprisingly, allows the user to log in. It brings in the AuthenticatesUsers trait, which brings in the RedirectsUsers and ThrottlesLogins traits.
Like the RegistrationController, the LoginController has a $redirectTo property that allows you to customize the path the user will be redirected to after a successful login. Everything else lives behind the AuthenticatesUsers trait.
The AuthenticatesUsers trait is responsible for showing users the login form, validating their logins, throttling failed logins, handling logouts, and redirecting users after a successful login.
The showLoginForm() method defaults to showing the user the auth.login view, but you can override it if you’d like it to use a different view.
The login() method accepts the POST from the login form. It validates the request using the validateLogin() method, which you can override if you’d like to customize the validation. It then hooks into the functionality of the ThrottlesLogins trait, which we’ll cover shortly, to reject users with too many failed logins. And finally, it redirects the user, either to her intended path (if the user was redirected to the login page when attempting to visit a page within the app) or to the path defined by the redirectPath() method, which returns your $redirectTo property.
The trait calls the empty authenticated() method after a successful login, so if you’d like to perform any sort of behavior in response to a successful login, just override this method in your LoginController.
There’s a username() method that defines which of your users columns is the “username”; this defaults to email but you can change that by overwriting the username() method in your controller to return the name of your username column.
And, like in the RegistersUsers trait, you can override the guard() method to define which auth guard (more on that in “Guards”) this controller should use.
The ThrottlesLogins trait is an interface to Laravel’s Illuminate\Cache\RateLimiter class, which is a utility to rate-limit any event using the cache. This trait applies rate limiting to user logins, limiting users from using the login form if they’ve had too many failed logins within a certain amount of time. This functionality does not exist in Laravel 5.1.
If you import the ThrottlesLogins trait, all of its methods are protected, which means they can’t actually be accessed as routes. Instead, the AuthenticatesUsers trait looks to see whether you’ve imported the ThrottlesLogins trait, and if so, it’ll attach its functionality to your logins without any work on your part. Since the default LoginController imports both, you’ll get this functionality for free if you use the auth scaffold.
ThrottlesLogins limits any given combination of username and IP address to 5 attempts per 60 seconds. Using the cache, it increments the “failed login” count of a given username/IP address combination, and if any user reaches 5 failed login attempts within 60 seconds, it redirects that user back to the login page with an appropriate error until the 60 seconds is over.
The ResetPasswordController simply pulls in the ResetsPasswords trait. This trait provides validation and access to basic password reset views, and then uses an instance of Laravel’s PasswordBroker class (or anything else implementing the PasswordBroker interface, if you choose to write your own) to handle sending password reset emails and actually resetting the passwords.
Just like the other traits we’ve covered, it handles showing the reset password view (showResetForm() shows the auth.passwords.reset view), and the POST request that is sent from that view (reset() validates and sends the appropriate response). The resetPassword() method actually resets the password, and you can customize the broker with broker() and the auth guard with guard().
If you’re interested in customizing any of this behavior, just override the specific method you want to customize in your controller.
The ForgotPasswordController simply pulls in the SendsPasswordResetEmails trait. It shows the auth.passwords.email form with the showLinkRequestForm() method, and handles the POST of that form with the sendResetLinkEmail() method. You can customize the broker with the broker() method.
The VerificationController pulls in the VerifiesEmails trait, which handles verifying the email address of newly-signed-up users. You can customize the path to send users to after validation.
Now that we have the auth controllers providing some methods for a series of pre-defined routes, we’ll want our users to actually be able to hit those routes. We could add all these routes manually to routes/web.php, but there’s already a convenience tool for that, called Auth::routes():
// routes/web.phpAuth::routes();
As you can probably guess, Auth::routes() brings in a bundle of predefined routes to your routes file. In Example 9-5 you can see the routes that are actually being defined there.
// Authentication Routes...$this->get('login','Auth\LoginController@showLoginForm')->name('login');$this->post('login','Auth\LoginController@login');$this->post('logout','Auth\LoginController@logout')->name('logout');// Registration Routes...$this->get('register','Auth\RegisterController@showRegistrationForm')->name('register');$this->post('register','Auth\RegisterController@register');// Password Reset Routes...$this->get('password/reset','Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request');$this->post('password/email','Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email');$this->get('password/reset/{token}','Auth\ResetPasswordController@showResetForm')->name('password.reset');$this->post('password/reset','Auth\ResetPasswordController@reset');// If email verification is enabled$this->get('email/verify','Auth\VerificationController@show')->name('verification.notice');$this->get('email/verify/{id}','Auth\VerificationController@verify')->name('verification.verify');$this->get('email/resend','Auth\VerificationController@resend')->name('verification.resend');
Basically, Auth::routes() includes the routes for authentication, registration, and password resets. As you can see, there are also optional routes for email verification, which was introduced in Laravel 5.7.
To enable Laravel’s email verification service, which requires new users to verify they have access to the email address they signed up with, update your Auth::routes call to enable it:
Auth::routes(['verify'=>true]);
At this point you have a migration, a model, controllers, and routes for your authentication system. But what about your views?
Laravel handles that by providing an auth scaffold (available since Laravel 5.2), which is intended to be run on a new application and provide you with even more skeleton code to get your auth system running quickly.
The auth scaffold takes care of adding Auth::routes() to your routes file, adds a view for each route, and creates a HomeController to serve as the landing page for logged-in users; it also routes to the index() method of HomeController at the /home URI.
Just run php artisan make:auth, and the following files will be made available to you:
app/Http/Controllers/HomeController.php resources/views/auth/login.blade.php resources/views/auth/register.blade.php resources/views/auth/verify.blade.php resources/views/auth/passwords/email.blade.php resources/views/auth/passwords/reset.blade.php resources/views/layouts/app.blade.php resources/views/home.blade.php
At this point, you have / returning the welcome view, /home returning the home view, and a series of auth routes for login, logout, registration, and password reset pointing to the auth controllers. Each of the seeded views has Bootstrap-based layouts and form fields for all necessary fields for login, registration, and password reset, and they already point to the correct routes.
At this point, you have all of the pieces in place for every step of the normal user registration and authentication flow. You can tweak all you want, but you’re entirely ready to register and authenticate users.
Let’s review quickly the steps from new site to full authentication system:
laravel new MyApp cd MyApp # edit your .env file to specify the correct database connection details php artisan make:auth php artisan migrate
That’s it. Run those commands, and you will have a landing page and a bootstrap-based user registration, login, logout, and password reset system, with a basic landing page for all authenticated users.
The auth scaffold has this implemented out of the box, but it’s still worth learning how it works and how to use it on your own. If you want to implement a “remember me”–style long-lived access token, make sure you have a remember_token column on your users table (which you will if you used the default migration).
When you’re normally logging in a user (and this is how the LoginController does it, with the AuthenticatesUsers trait), you’ll “attempt” an authentication with the user-provided information, like in Example 9-6.
if(auth()->attempt(['email'=>request()->input('email'),'password'=>request()->input('password'),])){// Handle the successful login}
This provides you with a user login that lasts as long as the user’s session. If you want Laravel to extend the login indefinitely using cookies (as long as the user is on the same computer and doesn’t log out), you can pass a boolean true as the second parameter of the auth()->attempt() method. Take a look at Example 9-7 to see what that request looks like.
if(auth()->attempt(['email'=>request()->input('email'),'password'=>request()->input('password'),]),request()->filled('remember')){// Handle the successful login}
You can see that we checked whether the input has a non-empty (“filled”) remember property, which will return a boolean. This allows our users to decide if they want to be remembered with a checkbox in the login form.
And later, if you need to manually check whether the current user was authenticated by a remember token, there’s a method for that: auth()->viaRemember() returns a boolean indicating whether or not the current user authenticated via a remember token. This will allow you to prevent certain higher-sensitivity features from being accessible by remember token, and you can require users to reenter their passwords.
The most common case for user authentication is that you’ll allow the users to provide their credentials, and then use auth()->attempt() to see whether the provided credentials match any real users. If so, you log them in.
But sometimes there are contexts where it’s valuable for you to be able to choose to log a user in on your own. For example, you may want to allow admin users to switch users.
There are four methods that make this possible. First, you can just pass a user ID:
auth()->loginUsingId(5);
Second, you can pass a User object (or any other object that implements the Illuminate\Contracts\Auth\Authenticatable contract):
auth()->login($user);
And third and fourth, you can choose to authenticate the given user for only the current request, which won’t impact your session or cookies at all, using once and onceUsingId:
auth()->once(['username'=>'mattstauffer']);// orauth()->onceUsingId(5);
Note that the array you pass to the once() method can contain any key/value pairs to uniquely identify the user you’d like to authenticate as. You can even pass multiple keys and values, if it’s what is appropriate for your project. For example:
auth()->once(['last_name'=>'Stauffer','zip_code'=>90210,])
If you ever need to log out a user manually, just call logout():
auth()->logout();
If you’d like to log out not just a user’s current session, but also those on any other devices, you’ll need to prompt the user for their password and pass it to the logoutOtherDevices method. In order to do this, you’ll have to add the (commented-out-by-default) AuthenticateSession middleware to your web group in app\Http\Kernel.php:
'web'=>[// ...\Illuminate\Session\Middleware\AuthenticateSession::class,],
And then you can use it inline anywhere you need:
auth()->logoutOtherDevices($password);
The logoutOtherDevices() method is only available in Laravel 5.6 and later.
In Example 9-3, you saw how to check whether visitors are logged in and redirect them if not. You could perform these sorts of checks on every route in your application, but it would very quickly get tedious. It turns out that route middleware (see Chapter 10 to learn more about how they work) are a perfect fit for restricting certain routes to guests or to authenticated users.
Once again, Laravel comes with the middleware we need to do this out of the box. You can see which route middleware you have defined in App\Http\Kernel:
protected$routeMiddleware=['auth'=>\Illuminate\Auth\Middleware\Authenticate::class,'auth.basic'=>\Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,'bindings'=>\Illuminate\Routing\Middleware\SubstituteBindings::class,'cache.headers'=>\Illuminate\Http\Middleware\SetCacheHeaders::class,'can'=>\Illuminate\Auth\Middleware\Authorize::class,'guest'=>\App\Http\Middleware\RedirectIfAuthenticated::class,'signed'=>\Illuminate\Routing\Middleware\ValidateSignature::class,'throttle'=>\Illuminate\Routing\Middleware\ThrottleRequests::class,'verified'=>\Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,];
Four of the default route middleware are authentication-related:
auth restricts route access to authenticated users
auth.basic restricts access to authenticated users using HTTP Basic Authentication
guest restricts access to unauthenticated users
can is used for authorizing user access to given routes
It’s most common to use auth for your authenticated-user-only sections and guest for any routes you don’t want authenticated users to see (like the login form). auth.basic is a much less commonly used middleware for authenticating via request headers.
Example 9-8 shows an example of a few routes protected by the auth middleware.
Route::middleware('auth')->group(function(){Route::get('account','AccountController@dashboard');});Route::get('login','Auth\LoginController@getLogin')->middleware('guest');
Laravel 5.7 introduced a new feature that makes it possible to require a user to verify that they have access to the email address they registered with.
In order to enable email verification, update your App\User and make it implement the Illuminate\Contracts\Auth\MustVerifyEmail contract.
classUserextendsAuthenticatableimplementsMustVerifyEmail{useNotifiable;// ...}
The user table must also contain a nullable timestamp column named email_verified_at, which the new default CreateUsersTable migration will have already provided for you in apps created in 5.7 or later.
You’ll also need to do one more thing: enable the email verification routes in your controller. The easiest method is to use Auth::routes in your routes file with the verify parameter set to true:
Auth::routes(['verify'=>true]);
Now, you can protect any routes you’d like from being accessed by any users who haven’t verified their email address:
Route::get('posts/create',function(){// Only verified users may enter...})->middleware('verified');
You can customize the route where users are redirected after verifying in your VerificationController:
protected$redirectTo='/profile';
If you want to check against a user’s authentication, not at the route level but in your views, you can, with @auth and @guest:
@auth// The user is authenticated@endauth@guest// The user is not authenticated@endguest
You can also specify which guard you’d like to use with both methods by passing the guard name as a parameter:
@auth('trainees')// The user is authenticated...@endauth@guest('trainees')// The user is not authenticated...@endguest
Every aspect of Laravel’s authentication system is routed through something called a guard. Each guard is a combination of two pieces: a driver that defines how it persists and retrieves the authentication state (for example, session), and a provider that allows you to get a user by certain criteria (for example, users).
Out of the box Laravel has two guards: web and api. web is the more traditional authentication style, using the session driver and the basic user provider. api also uses the same user provider, but it uses the token driver instead of the session to authenticate each request.
You’d change drivers if you wanted to handle the identification and persistence of a user’s identity differently (for example, changing from a long-running session to a provided-every-page-load token), and you’d change providers if you wanted to change the storage type or retrieval methods for your users (for example, moving to storing your users in Mongo instead of MySQL).
The guards are defined in config/auth.php, and you can change them, add new guards, and also define which guard will be the default there. For what it’s worth, this is a relatively uncommon configuration; most Laravel apps just use one guard.
The “default” guard defines which will be used any time you use any auth features without specifying a guard. For example, auth()->user() will pull the currently authenticated user using the default guard. You can change this guard by changing the auth.defaults.guard setting in config/auth.php:
'defaults'=>['guard'=>'web',// Change the default here'passwords'=>'users',],
If you’re using Laravel 5.1, you’ll notice that the structure of the authentication information is a little different from this. Don’t worry; the features all still work the same, they’re just structured differently.
You may have noticed that I refer to configuration sections with references like auth.defaults.guard. What that translates to is: in config/auth.php, in the array section keyed defaults, there should be a property keyed guard. That one is auth.defaults.guard.
If you want to use another guard, but not change the default, you can start your Auth calls with guard():
$apiUser=auth()->guard('api')->user();
This will, just for this call, get the current user using the api guard.
You can add a new guard at any time in config/auth.php, in the auth.guards setting:
'guards'=>['trainees'=>['driver'=>'session','provider'=>'trainees',],],
As you can see here, we’ve created a new guard (in addition to web and api) named trainees. Let’s imagine, for the rest of this section, that we’re building an app where our users are physical trainers and they each have their own users—trainees—who can log in to their subdomains. So, we need a separate guard for them.
The only two options for driver are token and session. Out of the box, the only option for provider is users, which supports authentication against your default users table, but you can create your own provider easily.
If you want to define a custom guard and your guard conditions (how to look up a given user against the request) can be described simply enough in response to any given HTTP request, you might just want to throw the user lookup code into a closure and not deal with creating a new custom guard class.
The viaRequest() auth method makes it possible to define a guard (named in the first parameter) using just a closure (defined in the second parameter) that takes the HTTP request and returns the appropriate user. To register a closure request guard, call viaRequest() in the boot() method of your AuthServiceProvider:
publicfunctionboot(){$this->registerPolicies();Auth::viaRequest('token-hash',function($request){returnUser::where('token-hash',$request->token)->first();});}
Just below where the guards are defined in config/auth.php, there’s an auth.providers section that defines the available providers. Let’s create a new provider named trainees:
'providers'=>['users'=>['driver'=>'eloquent','model'=>App\User::class,],'trainees'=>['driver'=>'eloquent','model'=>App\Trainee::class,],],
The two options for driver are eloquent and database; if you use eloquent, you’ll need a model property that contains an Eloquent class name (the model to use for your User class), and if you use database, you’ll need a table property to define which table it should authenticate against.
In our example, you can see that this application has a User and a Trainee, and they need to be authenticated separately. This way, the code can differentiate between auth()->guard('users’) and auth()->guard('trainees’).
One last note: the auth route middleware can take a parameter that is the guard name. So, you can guard certain routes with a specific guard:
Route::middleware('auth:trainees')->group(function(){// Trainee-only routes here});
The user provider creation flow just described still relies on the same UserProvider class, which means it’s expecting to pull the identifying information out of a relational database. But if you’re using Mongo or Riak or something similar, you’ll actually need to create your own class.
To do this, create a new class that implements the Illuminate\Contracts\Auth\UserProvider interface, and then bind it in AuthServiceProvider@boot:
auth()->provider('riak',function($app,array$config){// Return an instance of Illuminate\Contracts\Auth\UserProvider...returnnewRiakUserProvider($app['riak.connection']);});
We’ll talk more about events in Chapter 16, but Laravel’s event system is a basic pub/sub framework. There are system- and user-generated events that are broadcast, and the user has the ability to create event listeners that do certain things in response to certain events.
So, what if you wanted to send a ping to a particular security service every time a user was locked out after too many failed login attempts? Maybe this service watches out for a certain number of failed logins from certain geographic regions, or something else. You could, of course, inject a call in the appropriate controller. But with events, you can just create an event listener that listens to the “user locked out” event, and register that.
Take a look at Example 9-9 to see all of the events that the authentication system emits.
protected$listen=['Illuminate\Auth\Events\Registered'=>[],'Illuminate\Auth\Events\Attempting'=>[],'Illuminate\Auth\Events\Authenticated'=>[],'Illuminate\Auth\Events\Login'=>[],'Illuminate\Auth\Events\Failed'=>[],'Illuminate\Auth\Events\Logout'=>[],'Illuminate\Auth\Events\Lockout'=>[],'Illuminate\Auth\Events\PasswordReset'=>[],];
As you can see, there are listeners for “user registered”, “user attempting login,” “user authenticated,” “successful login,” “failed login”, “logout,” “lockout,” and “password reset.” To learn more about how to build event listeners for these events, check out Chapter 16.
Finally, let’s cover Laravel’s authorization system. It enables you to determine whether a user is authorized to do a particular thing, which you’ll check using a few primary verbs: can, cannot, allows, and denies. The access control list (ACL) system was introduced in Laravel 5.2.
Most of this authorization control will be performed using the Gate facade, but there are also convenience helpers in your controllers, on the User model, as middleware, and available as Blade directives. Take a look at this example to get a taste of what we’ll be able to do:
if(Gate::denies('edit-contact',$contact)){abort(403);}if(!Gate::allows('create-contact',Contact::class)){abort(403);}
The default place to define authorization rules is the boot() method of the AuthServiceProvider, where you’ll be calling methods on the Auth facade.
An authorization rule is called an ability, and is comprised of two things: a string key (e.g., update-contact) and a closure that returns a boolean. Example 9-10 shows an ability for updating a contact.
classAuthServiceProviderextendsServiceProvider{publicfunctionboot(){$this->registerPolicies();Gate::define('update-contact',function($user,$contact){return$user->id==$contact->user_id;});}}
Let’s walk through the steps for defining an ability.
First, you want to define a key. In naming this key, you should consider what string makes sense in your code’s flow to refer to the ability you’re providing the user. You can see the code sample uses the convention {verb}-{modelName}: create-contact, update-contact, etc.
Second, you define the closure. The first parameter will be the currently authenticated user, and all parameters after that will be the object(s) you’re checking for access to—in this instance, the contact.
So, given those two objects, we can check whether the user is authorized to update this contact. You can write this logic however you want, but in the app we’re looking at at the moment, authorization depends on being the creator of the contact row. The closure will return true (authorized) if the current user created the contact, and false (unauthorized) if not.
Just like with route definitions, you could also use a class and method instead of a closure to resolve this definition:
$gate->define('update-contact','ContactACLChecker@updateContact');
Now that you’ve defined an ability, it’s time to test against it. The simplest way is to use the Gate facade, as in Example 9-11 (or you can inject an instance of Illuminate\Contracts\Auth\Access\Gate).
if(Gate::allows('update-contact',$contact)){// Update contact}// or...if(Gate::denies('update-contact',$contact)){abort(403);}
You might also define an ability with multiple parameters—maybe a contact can be in groups, and you want to authorize whether the user has access to add a contact to a group. Example 9-12 shows how to do this.
// DefinitionGate::define('add-contact-to-group',function($user,$contact,$group){return$user->id==$contact->user_id&&$user->id==$group->user_id;});// Usageif(Gate::denies('add-contact-to-group',[$contact,$group])){abort(403);}
And if you need to check authorization for a user other than the currently authenticated user, try forUser(), like in Example 9-13.
if(Gate::forUser($user)->denies('create-contact')){abort(403);}
The most common use for ACL is to define access to individual “resources” (think an Eloquent model, or something you’re allowing users to administer from their admin panel).
The resource() method makes it possible to apply the four most common gates, view, create, update, and delete, to a single resource at once:
Gate::resource('photos','App\Policies\PhotoPolicy');
This is equivalent to defining the following:
Gate::define('photos.view','App\Policies\PhotoPolicy@view');Gate::define('photos.create','App\Policies\PhotoPolicy@create');Gate::define('photos.update','App\Policies\PhotoPolicy@update');Gate::define('photos.delete','App\Policies\PhotoPolicy@delete');
If you want to authorize entire routes, you can use the Authorize middleware (which has a shortcut of can), like in Example 9-14.
Route::get('people/create',function(){// Create person...})->middleware('can:create-person');Route::get('people/{person}/edit',function(){// Edit a person...})->middleware('can:edit,person');
Here, the {person} parameter (whether it’s defined as a string or as a bound route model) will be passed to the ability method as an additional parameter.
The first check in Example 9-14 is a normal ability, but the second is a policy, which we’ll talk about in “Policies”.
If you need to check for an action that doesn’t require a model instance (for example, create, unlike edit, doesn’t get passed an actual route-model-bound instance), you can just pass the classname:
Route::post('people',function(){// Create a person...})->middleware('can:create,App\Person');
The parent App\Http\Controllers\Controller class in Laravel imports the AuthorizesRequests trait, which provides three methods for authorization: authorize(), authorizeForUser(), and authorizeResource().
authorize() takes an ability key and an object (or array of objects) as parameters, and if the authorization fails, it’ll quit the application with a 403 (Unauthorized) status code. That means this feature can turn three lines of authorization code into just one, as you can see in Example 9-15.
// From this:publicfunctionedit(Contact$contact){if(Gate::cannot('update-contact',$contact)){abort(403);}returnview('contacts.edit',['contact'=>$contact]);}// To this:publicfunctionedit(Contact$contact){$this->authorize('update-contact',$contact);returnview('contacts.edit',['contact'=>$contact]);}
authorizeForUser() is the same, but allows you to pass in a User object instead of defaulting to the currently authenticated user:
$this->authorizeForUser($user,'update-contact',$contact);
authorizeResource(), called once in the controller constructor, maps a predefined set of authorization rules to each of the RESTful controller methods in that controller—something like Example 9-16.
...classContactsControllerextendsController{publicfunction__construct(){// This call does everything you see in the methods below.// If you put this here, you can remove all authorize// calls in the individual resource methods here.$this->authorizeResource(Contact::class);}publicfunctionindex(){$this->authorize('view',Contact::class);}publicfunctioncreate(){$this->authorize('create',Contact::class);}publicfunctionstore(Request$request){$this->authorize('create',Contact::class);}publicfunctionshow(Contact$contact){$this->authorize('view',$contact);}publicfunctionedit(Contact$contact){$this->authorize('update',$contact);}publicfunctionupdate(Request$request,Contact$contact){$this->authorize('update',$contact);}publicfunctiondestroy(Contact$contact){$this->authorize('delete',$contact);}}
If you’re not in a controller, you’re more likely to be checking the capabilities of a specific user than the currently authenticated user. That’s already possible with the Gate facade using the forUser() method, but sometimes the syntax can feel a little off.
Thankfully, the Authorizable trait on the User class provides three methods to make a more readable authorization feature: $user->can(), $user->cant(), and $user->cannot(). As you can probably guess, cant() and cannot() do the same thing, and can() is their exact inverse.
That means you can do something like Example 9-17.
$user=User::find(1);if($user->can('create-contact')){// do something}
Behind the scenes, these methods are just passing your params to Gate; in the preceding example, Gate::forUser($user)->check('create-contact').
Blade also has a little convenience helper: a @can directive. Example 9-18 illustrates its usage.
<nav><ahref="/">Home</a>@can('edit-contact', $contact)<ahref="{{ route('contacts.edit', [$contact->id]) }}">Edit This Contact</a>@endcan</nav>
You can also use @else in between @can and @endcan, and you can use @cannot and @endcannot as in Example 9-19.
<h1>{{ $contact->name }}</h1>@cannot('edit-contact', $contact) LOCKED @endcannot
If you’ve ever built an app with an admin user class, you’ve probably looked at all of the simple authorization closures so far in this chapter and thought about how you could add a superuser class that overrides these checks in every case. Thankfully, there’s already a tool for that.
In AuthServiceProvider, where you’re already defining your abilities, you can also add a before() check that runs before all the others and can optionally override them, like in Example 9-20.
Gate::before(function($user,$ability){if($user->isOwner()){returntrue;}});
Note that the string name for the ability is also passed in, so you can differentiate your before hooks based on your ability naming scheme.
Up until this point, all of the access controls have required you to manually associate Eloquent models with the ability names. You could have created an ability named something like visit-dashboard that’s not related to a specific Eloquent model, but you’ll probably have noticed that most of our examples have had to do with doing something to something—and in most of these cases, the something that’s the recipient of the action is an Eloquent model.
Authorization policies are organizational structures that help you group your authorization logic based on the resource you’re controlling access to. They make it easy to manage defining authorization rules for behavior toward a particular Eloquent model (or other PHP class), all together in a single location.
Policies are PHP classes, which can be generated with an Artisan command:
php artisan make:policy ContactPolicy
Once they’re generated, they need to be registered. The AuthServiceProvider has a $policies property, which is an array. The key of each item is the class name of the protected resource (usually, but not always, an Eloquent class), and the value is the policy class name:
classAuthServiceProviderextendsServiceProvider{protected$policies=[Contact::class=>ContactPolicy::class,];
A policy class that’s generated by Artisan doesn’t have any special properties or methods. But every method that you add is now mapped as an ability key for this object.
Let’s define an update() method to take a look at how it works (Example 9-21).
<?phpnamespaceApp\Policies;classContactPolicy{publicfunctionupdate($user,$contact){return$user->id==$contact->user_id;}}
Notice that the contents of this method look exactly like they would in a Gate definition.
In Laravel 5.2, if you want to define a policy method that relates to the class but not a specific instance—for example, “can this user create contacts at all?” rather than just “can this user view this specific contact?”—create that method and add “Any” at the end of its name:
...classContactPolicy{publicfunctioncreateAny($user){return$user->canCreateContacts();}
In Laravel 5.3, you can drop the Any suffix and treat this just like a normal method.
If there’s a policy defined for a resource type, the Gate will use the first parameter to figure out which method to check on your policy. If you run Gate::allows('update', $contact), it will check the ContactPolicy@update method for authorization.
This also works for the Authorize middleware and for User model checking and Blade checking, as seen in Example 9-22.
// Gateif(Gate::denies('update',$contact)){abort(403);}// Gate if you don't have an explicit instanceif(!Gate::check('create',Contact::class)){abort(403);}// Userif($user->can('update',$contact)){// Do stuff}// Blade@can('update',$contact)<!--showstuff-->@endcan
Additionally, there’s a policy() helper that allows you to retrieve a policy class and run its methods:
if(policy($contact)->update($user,$contact)){// Do stuff}
Just like with normal ability definitions, policies can define a before() method that allows you to override any call before it’s even processed (see Example 9-23).
publicfunctionbefore($user,$ability){if($user->isAdmin()){returntrue;}}
Application tests often need to perform a particular behavior on behalf of a particular user. We therefore need to be able to authenticate as a user in application tests, and we need to test authorization rules and authentication routes.
Of course, it’s possible to write an application test that manually visits the login page and then fills out the form and submits it, but that’s not necessary. Instead, the simplest option is to use the ->be() method to simulate being logged in as a user. Take a look at Example 9-24.
publicfunctiontest_it_creates_a_new_contact(){$user=factory(User::class)->create();$this->be($user);$this->post('contacts',['email'=>'my@email.com']);$this->assertDatabaseHas('contacts',['email'=>'my@email.com','user_id'=>$user->id,]);}
You can also use, and chain, the actingAs() method instead of be(), if you prefer how it reads:
publicfunctiontest_it_creates_a_new_contact(){$user=factory(User::class)->create();$this->actingAs($user)->post('contacts',['email'=>'my@email.com']);$this->assertDatabaseHas('contacts',['email'=>'my@email.com','user_id'=>$user->id,]);}
We can also test authorization like in Example 9-25.
publicfunctiontest_non_admins_cant_create_users(){$user=factory(User::class)->create(['admin'=>false]);$this->be($user);$this->post('users',['email'=>'my@email.com']);$this->assertDatabaseMissing('users',['email'=>'my@email.com']);}
Or we can test for a 403 response like in Example 9-26.
publicfunctiontest_non_admins_cant_create_users(){$user=factory(User::class)->create(['admin'=>false]);$this->be($user);$response=$this->post('users',['email'=>'my@email.com']);$response->assertStatus(403);}
We need to test that our authentication (sign up and sign in) routes work too, as illustrated in Example 9-27.
publicfunctiontest_users_can_register(){$this->post('register',['name'=>'Sal Leibowitz','email'=>'sal@leibs.net','password'=>'abcdefg123','password_confirmation'=>'abcdefg123',]);$this->assertDatabaseHas('users',['name'=>'Sal Leibowitz','email'=>'sal@leibs.net',]);}publicfunctiontest_users_can_log_in(){$user=factory(User::class)->create(['password'=>Hash::make('abcdefg123')]);$this->post('login',['email'=>$user->,'password'=>'abcdefg123',]);$this->assertTrue(auth()->check());$this->assertTrue($user->is(auth()->user()));}
You could also use the integration test features to direct the test to “click” your authentication fields and “submit” the fields to test the entire flow. We’ll talk about that more in Chapter 12.
In projects running versions of Laravel prior to 5.4, assertDatabaseHas should be replaced by seeInDatabase, assertDatabaseMissing should be replaced by dontSeeInDatabase, assertDatabaseHas should be replaced by seeInDatabase, and assertStatus should be called on $this instead of $response.
Between the default User model, the create_users_table migration, the auth controllers, and the auth scaffold, Laravel comes with a full user authentication system out of the box. The RegisterController handles user registration, the LoginController handles user authentication, and the ResetPasswordController and the ForgotPasswordController handle password resets. Each has certain properties and methods that can be used to override some of the default behavior.
The Auth facade and the auth() global helper provide access to the current user (auth()->user()) and makes it easy to check whether a user is logged in (auth()->check() and auth()->guest()).
Laravel also has an authorization system built in that allows you to define specific abilities (create-contact, visit-secret-page) or define policies for user interaction with entire models.
You can check for authorization with the Gate facade, the ->can() and ->cannot() methods on the User class, the @can and @cannot directives in Blade, the ->authorize() methods on the controller, or the can middleware.