Access all tutorials in sprocket icon.
August 6, 2015 from author Bill Keck.
Update December 21, 2015 for Laravel 5.2
Just a quick note that Laravel 5.2 now features an artisan command:
php artisan make:auth
This will create the basic views you need for login, registration, and password resets.
You might still want to read through this tutorial, it will give you an idea on how the AuthController works, which is essential later on when you want to modify it. Also, I provide the routes that you will need and we cover the redirectTo property, which will you most likely need as well.
Here is the original tutorial:
User login and registration for laravel 5.1.4 tutorial
In this tutorial, we are going to build the basic user login and registration routes and views. We assume you have a fresh install of Laravel 5.1.4. If you need help setting that up, please reference my Laravel Installation tutorial or visit the laravel.com docs.
We also assume that you have setup your database and run the first migration. If not, please read my tutorial on Database Setup in Laravel 5.1.
Also note, we are going to use Gists, which are online code snippets hosted by Github, for the views, so this will make copy and pasting of the views very easy.
Before we begin, let’s talk about the difference between laravel 5.0 and 5.1 and 5.1.4.
Background
5.0 came with a working user registration and password implementation right out of the box. In some ways, it was very convenient because it gave you the routes, model, controllers, and views that you needed to have instant registration and login. In other ways, it was an awkward implementation. It used, for example, the following route declaration:
Route::controllers([
'auth' => 'Auth\AuthController',
'password' => 'Auth\PasswordController',
]);
In this way of doing routing, the verbs on the controller define the type of route, for example:
public function getRegister()
So if you typed in a url such as yourdomain.com/auth/register, it would send you to the getRegister method on the controller. This is perhaps one of the less intuitive and more complicated ways of doing this.
To complicate things even further, the AuthController used a trait to pull in the getRegister method, so that was not even visible directly in the controller.
Taylor Otwell has made an artform out of making things simpler, and this just seemed way overly-complicated.
So when 5.1 released, I expected things to get simpler, and they did to some extent, but they also got a little stranger.
The AuthController was still using traits, but dropped the registrar class, which had formerly created the user, in favor of a more direct approach. That made it a little simpler.
At the same time, none of the routes or the views for login and registration, which came ready-made out of the box in 5.0 were included in 5.1. So unless you were simply upgrading from 5.0 to 5.1, you had to build all that from scratch.
So I wrote this tutorial for 5.1 and eliminated the main trait to make it easier to work with, but then guess what? Within 2 months 5.1.4 was released and Laravel introduced a new feature called login throttling, which made quite a few changes to the out-of-the-box user login and registration solution.
This meant that anyone following this tutorial was following something that was not going to work if they were using a fresh install of Laravel. So having learned from that mistake, I’ve decided to update this tutorial and preserve the original traits that come out of the box. That way if there is another change to a subsequent version, it should not break the code.
I think it’s best to “ride the lightning” of Taylor Otwell’s inventiveness and make the application as easy to maintain as possible. If you are unfamiliar with traits, you will learn a little about them in this tutorial. You can also check out this tutorial on traits.
Ok, here we go.
Part 1. The Routes
Let’s start by adding the following routes in routes file;
// Authentication routes...
Route::get('auth/login', 'Auth\AuthController@getLogin');
Route::post('auth/login', 'Auth\AuthController@postLogin');
Route::get('auth/logout', 'Auth\AuthController@getLogout');
// Registration routes...
Route::get('auth/register', 'Auth\AuthController@getRegister');
Route::post('auth/register', 'Auth\AuthController@postRegister');
Route::controllers([
'password' => 'Auth\PasswordController',
]);
You can see we made a get and post route for both register and login and a get route for logout. We are also going to include a route for our PasswordController, which is also included out of the box. I have a separate tutorial for implementation of the forgot password functions, which I will also have to update for this latest version.
Part 2. AuthController
You do not need to create this file, it comes out-of-the-box, you can find it in app/Http/Controllers/Auth:
<?php
namespace App\Http\Controllers\Auth;
use App\User;
use Validator;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ThrottlesLogins;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
class AuthController extends Controller
{
/*
|--------------------------------------------------------------------------
| Registration & Login Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users, as well as the
| authentication of existing users. By default, this controller uses
| a simple trait to add these behaviors. Why don't you explore it?
|
*/
use AuthenticatesAndRegistersUsers, ThrottlesLogins;
/**
* Create a new authentication controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest', ['except' => 'getLogout']);
}
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'password' => 'required|confirmed|min:6',
]);
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
}
}
While we’re here, we going to add one property that defines our redirectTo path:
private $redirectTo = '/';
So put that directly under the line:
use AuthenticatesAndRegistersUsers, ThrottlesLogins;
Ok, you might be asking, where are all the methods for the login and registration actions? As in the previous versions, these are in the traits. We will return to those in a moment, but let’s first walk through what we have in the AuthController class.
I’ll skip the use statements and class declaration, those are pretty self-explanatory.
So moving along, we have the constructor:
public function __construct()
{
$this->middleware('guest', ['except' => 'getLogout']);
}
What it’s doing here is limiting access to the methods to guests, except for the getLogout method. Obviously if you already authenticated, you do not need to login or register. However you would need to be able to logout, which is why we have this exception.
This is a beautiful example of the Laravel middleware at work. I will go deeper into that in a separate tutorial.
Next we have a validator method:
protected function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'password' => 'required|confirmed|min:6',
]);
}
This is setting the rules for the form inputs. Obviously, if you want more fields on your form, you would add more rules here, and also to your DB and User model.
Next we have the create method:
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
}
This method creates the new user upon registration.
Ok, so you can see there isn’t much on the surface of the AuthController. That’s because most of the methods are in the traits that we are using. We will cover those next.
Part 3. The Traits
Ok, so for anyone who doesn’t know, a trait is a class that can be used by multiple classes in a way that is like inheritance. You simply call the trait through the use statement and then all of the methods of the trait are available to the class that is using it. It’s a really cool and efficient way to handle code that will be reused by multiple classes.
I’m covering this to give you a quick idea of how it all works. We are not changing anything here however, so you can skip this part if you are in a hurry and want to go directly into the views.
Anyway, you can see AuthController uses 2 traits:
use AuthenticatesAndRegistersUsers, ThrottlesLogins;
Let’s look at:
namespace Illuminate\Foundation\Auth;
trait AuthenticatesAndRegistersUsers
{
use AuthenticatesUsers, RegistersUsers {
AuthenticatesUsers::redirectPath insteadof RegistersUsers;
}
}
You can see this trait just pulls in two other traits, and also declares which method to use in case of conflict, since both traits, AuthenticatesUsers and RegistersUsers have a redirectPath method.
Ok, so we go another layer into AuthenticatesUsers:
<?php
namespace Illuminate\Foundation\Auth;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Lang;
use Illuminate\Support\Facades\Cache;
trait AuthenticatesUsers
{
use RedirectsUsers;
/**
* Show the application login form.
*
* @return \Illuminate\Http\Response
*/
public function getLogin()
{
if (view()->exists('auth.authenticate')) {
return view('auth.authenticate');
}
return view('auth.login');
}
/**
* Handle a login request to the application.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function postLogin(Request $request)
{
$this->validate($request, [
$this->loginUsername() => 'required', 'password' => 'required',
]);
// If the class is using the ThrottlesLogins trait, we can automatically throttle
// the login attempts for this application. We'll key this by the username and
// the IP address of the client making these requests into this application.
$throttles = $this->isUsingThrottlesLoginsTrait();
if ($throttles && $this->hasTooManyLoginAttempts($request)) {
return $this->sendLockoutResponse($request);
}
$credentials = $this->getCredentials($request);
if (Auth::attempt($credentials, $request->has('remember'))) {
return $this->handleUserWasAuthenticated($request, $throttles);
}
// If the login attempt was unsuccessful we will increment the number of attempts
// to login and redirect the user back to the login form. Of course, when this
// user surpasses their maximum number of attempts they will get locked out.
if ($throttles) {
$this->incrementLoginAttempts($request);
}
return redirect($this->loginPath())
->withInput($request->only($this->loginUsername(), 'remember'))
->withErrors([
$this->loginUsername() => $this->getFailedLoginMessage(),
]);
}
/**
* Send the response after the user was authenticated.
*
* @param \Illuminate\Http\Request $request
* @param bool $throttles
* @return \Illuminate\Http\Response
*/
protected function handleUserWasAuthenticated(Request $request, $throttles)
{
if ($throttles) {
$this->clearLoginAttempts($request);
}
if (method_exists($this, 'authenticated')) {
return $this->authenticated($request, Auth::user());
}
return redirect()->intended($this->redirectPath());
}
/**
* Get the needed authorization credentials from the request.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
protected function getCredentials(Request $request)
{
return $request->only($this->loginUsername(), 'password');
}
/**
* Get the failed login message.
*
* @return string
*/
protected function getFailedLoginMessage()
{
return Lang::has('auth.failed')
? Lang::get('auth.failed')
: 'These credentials do not match our records.';
}
/**
* Log the user out of the application.
*
* @return \Illuminate\Http\Response
*/
public function getLogout()
{
Auth::logout();
return redirect(property_exists($this, 'redirectAfterLogout') ? $this->redirectAfterLogout : '/');
}
/**
* Get the path to the login route.
*
* @return string
*/
public function loginPath()
{
return property_exists($this, 'loginPath') ? $this->loginPath : '/auth/login';
}
/**
* Get the login username to be used by the controller.
*
* @return string
*/
public function loginUsername()
{
return property_exists($this, 'username') ? $this->username : 'email';
}
/**
* Determine if the class is using the ThrottlesLogins trait.
*
* @return bool
*/
protected function isUsingThrottlesLoginsTrait()
{
return in_array(
ThrottlesLogins::class, class_uses_recursive(get_class($this))
);
}
}
And you can see this trait has all of our login methods. I’m not going to cover these in detail because we are not changing anything and we are simply using what is provided.
Let’s take a quick look at the RegistersUsers trait:
<?php
namespace Illuminate\Foundation\Auth;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
trait RegistersUsers
{
use RedirectsUsers;
/**
* Show the application registration form.
*
* @return \Illuminate\Http\Response
*/
public function getRegister()
{
return view('auth.register');
}
/**
* Handle a registration request for the application.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function postRegister(Request $request)
{
$validator = $this->validator($request->all());
if ($validator->fails()) {
$this->throwValidationException(
$request, $validator
);
}
Auth::login($this->create($request->all()));
return redirect($this->redirectPath());
}
}
And so this gives us the methods we need to register a user.
Obviously a benefit to separating out authenticating and registering into separate traits is a less bulky controller and a more focused codebase.
Our AuthController is also using the ThrottlesLogins trait:
<?php
namespace Illuminate\Foundation\Auth;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Lang;
use Illuminate\Support\Facades\Cache;
trait ThrottlesLogins
{
/**
* Determine if the user has too many failed login attempts.
*
* @param \Illuminate\Http\Request $request
* @return bool
*/
protected function hasTooManyLoginAttempts(Request $request)
{
$attempts = $this->getLoginAttempts($request);
$lockedOut = Cache::has($this->getLoginLockExpirationKey($request));
if ($attempts > $this->maxLoginAttempts() || $lockedOut) {
if (! $lockedOut) {
Cache::put(
$this->getLoginLockExpirationKey($request), time() + $this->lockoutTime(), 1
);
}
return true;
}
return false;
}
/**
* Get the login attempts for the user.
*
* @param \Illuminate\Http\Request $request
* @return int
*/
protected function getLoginAttempts(Request $request)
{
return Cache::get($this->getLoginAttemptsKey($request)) ?: 0;
}
/**
* Increment the login attempts for the user.
*
* @param \Illuminate\Http\Request $request
* @return int
*/
protected function incrementLoginAttempts(Request $request)
{
Cache::add($key = $this->getLoginAttemptsKey($request), 1, 1);
return (int) Cache::increment($key);
}
/**
* Redirect the user after determining they are locked out.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
protected function sendLockoutResponse(Request $request)
{
$seconds = (int) Cache::get($this->getLoginLockExpirationKey($request)) - time();
return redirect($this->loginPath())
->withInput($request->only($this->loginUsername(), 'remember'))
->withErrors([
$this->loginUsername() => $this->getLockoutErrorMessage($seconds),
]);
}
/**
* Get the login lockout error message.
*
* @param int $seconds
* @return string
*/
protected function getLockoutErrorMessage($seconds)
{
return Lang::has('auth.throttle')
? Lang::get('auth.throttle', ['seconds' => $seconds])
: 'Too many login attempts. Please try again in '.$seconds.' seconds.';
}
/**
* Clear the login locks for the given user credentials.
*
* @param \Illuminate\Http\Request $request
* @return void
*/
protected function clearLoginAttempts(Request $request)
{
Cache::forget($this->getLoginAttemptsKey($request));
Cache::forget($this->getLoginLockExpirationKey($request));
}
/**
* Get the login attempts cache key.
*
* @param \Illuminate\Http\Request $request
* @return string
*/
protected function getLoginAttemptsKey(Request $request)
{
$username = $request->input($this->loginUsername());
return 'login:attempts:'.md5($username.$request->ip());
}
/**
* Get the login lock cache key.
*
* @param \Illuminate\Http\Request $request
* @return string
*/
protected function getLoginLockExpirationKey(Request $request)
{
$username = $request->input($this->loginUsername());
return 'login:expiration:'.md5($username.$request->ip());
}
/**
* Get the maximum number of login attempts for delaying further attempts.
*
* @return int
*/
protected function maxLoginAttempts()
{
return property_exists($this, 'maxLoginAttempts') ? $this->maxLoginAttempts : 5;
}
/**
* The number of seconds to delay further login attempts.
*
* @return int
*/
protected function lockoutTime()
{
return property_exists($this, 'lockoutTime') ? $this->lockoutTime : 60;
}
}
I’m not going to cover the entire trait, but I will say this is a cool feature that they have added to the login methods. It helps cut down on attacks from bots that will attempt to login over and over until it finds a successful user/pass combo.
It’s also easy to control the settings. You can see for example, that if you wanted to set the maxLoginAttempts to a number other than 5, you can include on your AuthController, a property:
private $maxLoginAttempts = 10;
That would get returned in the maxLoginAttempts method in the ThrottesLogins trait that the AuthController is using.
Part 4. Views
Ok, so now we need the views to support the registration and login forms.
A quick note about the app master page. A master page is a view file that would typically get included on every page. But instead of including it on other view pages, it works in reverse. The view pages are injected into the master page at specific locations.
I will write a separate tutorial for this, explaining it in more detail. For now, just know that we will be including the following into our views:
@extends('layouts.master')
@section('content')
// our view content goes here
@endsection
If you don’t have a master.blade.php file residing in a layouts folder inside of views, then don’t use this syntax at all and just use the what goes in between the @section tags.
Also note, that while we will be including a forgot password link on our login form, we have not yet built that functionality. I will build that will be a separate tutorial.
Ok, let’s create a couple of folders within our view folder. Our view folder is located at app/resources/views.
Make an auth folder and an email folder, each folder should reside directly under the views folder, so they have the following paths:
app/resources/views/auth
app/resources/views/email
We are creating the email folder because we will need it later in a subsequent tutorial, for now you can put an empty stub in there named password.blade.php.
Within the auth folder, make a register.blade.php file. For those unfamiliar, the convention we follow to use the blade template engine is viewname.blade.php. That tells the compiler it is a blade file and compiles it for you.
I’m going to provide a gist here, which has the code and will open in a new tab. You can copy the code from here:
Registration View
A lot of this is just straight Twitter Boostrap, so if you are unfamiliar with that, I suggest you take some time to visit getbootstrap. Our tutorial is not going to teach you html or css. We will be focusing on the blade specific elements and the content specific to our application, in this case the registration form.
Ok, so the first item of interest to us in the Gist is the if statement that holds the errors. Here we are using Blade’s @if syntax to make the interspersing of html and php a pleasure to work with.
As you might already be aware, the $errors object is always available to the views. That means we can use a simple foreach loop to parse and print. So if the count of errors is greater than 0, then we serve a notice:
Whoops! There were some problems with your input.
We follow that with the @foreach, using $error as the local variable, then we use the {{ }} syntax, which is short for , to print each error as a list item.
Normally what you will do is extract this block out to view partial and just include it with the line:
@inlcude(‘errors.errors’)
This would assume you have an errors folder inside the views folder with view file named errors.blade.php.
For this tutorial, we are leaving it in the main view, but you’ll certainly want to extract as I’ve described above because it doesn’t make sense to repeat this code.
Ok, let’s move on to the form itself. We open it with:
<form class="form-horizontal" role="form" method="POST" action="/auth/register">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
Note that there are two ways to begin the form. The manual way is how we are doing it here. But Laravel also has a helper class that allows you to do it as follows:
{!! Form::open(array('url' => '/auth/register, 'class' => 'form-horizontal')) !!}
You can see this is a lot simpler and it includes the CSRF token for you automatically.
If you want to do it that way, you need to pull in the illuminate Html helper class, which you can find at:
https://github.com/illuminate/html
Just follow the instructions there.
Anyway, for now we are going to stick with:
<form class="form-horizontal" role="form" method="POST" action="/auth/register">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
Next we have our form-group for the name input field. Refer to the Gist for the code.
You can see we also have a value of {{ old(‘name’) }} so that the form remembers the input. Otherwise, this is just standard bootstrap form syntax.
Next we have the two password fields, so the user is typing in what they intend and not making a typo. But how does it know to compare the two password fields and validate them?
IF you remember, we had in our validator method in our AuthController:
'password' => 'required|confirmed|min:6',
You can see the middle rule is confirmed, so it’s looking for password_confirmation from the form post and performs its validation. This is unbelievably easy to work with, since you don’t have to set up a class property or do anything else for it.
If you wanted to confirm any other input, you would simply use the _confirmation syntax on the 2nd field and then make sure you had confirmed as one of the validation rules.
Finally, we have the submit button and closing form tag and that should get you a working registration form.
Note: If for some reason you passing validation but not saving to the DB, check the $fillable property on your User class, which is located in the app directory. You have to explicitly name the properties which are mass assignable.
protected $fillable => ['name', 'email', 'password'];
Ok, let’s move on to the login view. Let’s create a login.blade.php file within the app/resources/views/auth folder with the following contents:
Login Gist
Ok, so we have the errors section again, which just points to how much better it would be in a partial, as I described in the registration view section. Then we come to the login form itself, which starts as we would expect:
<form class="form-horizontal" role="form" method="POST" action="/auth/login">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
Next we get the email and password fields, nothing new here, so I won’t list it again.
Then we get a checkbox for the remember me, which if checked will set a cookie. The value of remember gets passed in the Auth::attempt method in the AuthenticatesUsers trait:
if (Auth::attempt($credentials, $request->has('remember'))) {
return $this->handleUserWasAuthenticated($request, $throttles);
}
Then we get the submit button and forgot password link and that should be it, you should now have a working user registration and login. You should be able to test the following routes:
yourapp.com/auth/login
yourapp.com/auth/register
yourapp.com/auth/logout
I hope you have enjoyed this tutorial and found it useful. Click on the sprocket icon at the top of the page to see all tutorials. Please comment, share, and like if you can, thanks.
I don’t have a donate button, but If you would like to support my work and learn more about Laravel, you can do so by buying one of my books, Laraboot: laravel 5* For Beginners, I really appreciate it.