KodeBLOG

Kode Blog - Inspiring And Empowering Developers.

Home About Us Courses Categories Blog Contact Us

Laravel Authentication with Password Reset

Introduction

Authenticating users in Laravel is like taking a walk in the park. The entire authentication system can be created using a single artisan command. In this tutorial, we are going to use artisan to scaffold the authentication. We will then customize the views that Laravel generates, disable registration and create custom notifications for password reset messages.

We will also look at user roles. For example, an administrator will be able to perform all actions in the system while the shop keeper will only be able to perform tasks that they have been authorized to perform only. We will use a third-party package to accomplish that.

By the end of this tutorial, you would have known how to customized the authentication system generated by Laravel and implement user roles.

The following screen shots shows you what you will build by the end of this tutorial.

Laravel User Roles

Topics to be covered

In this tutorial, we will cover the following topics

  • Tutorial prerequisites
  • Laravel Authentication Basics
    • Customizing Laravel Authentication Routes
    • Customizing Laravel Authentication Views
    • Custom Email Reset Notification
  • Laravel sending fake smtp emails
  • Laravel Entrust – Role Based Permissions
    • Installation & configuration
    • Migrations
    • Routes Middleware
  • Larashop Admin Panel User Roles and Permissions

Tutorial prerequisites

This tutorial assumes;

  • You understand the basics of Laravel 5. If you do then I recommend you start with these Laravel 5 Tutorial series.
  • You have PHP, MySQL and a web server i.e. Apache up and running
  • Laravel Homestead – this is optional but provides a great development environment. If you are new to Laravel homestead then I recommend you read the series on Laravel Homestead
  • You have composer installed
  • You have a text editor or IDE that supports PHP.
  • You have a modern web browser that supports HTML5
  • Git – this is optional but highly recommended. This tutorial has a repository on GitHub, with Git, you will be able to clone starter code for each tutorial.

Tutorial Starter Code

This tutorial builds on from the previous tutorial. If you have not been following the tutorial series, then you can download starter code for this tutorial from GitHub

If you have git installed, run the following command to clone the project

git clone -b 06_exception_handling https://github.com/KodeBlog/Laradmin.git laradmin

HERE,

  • git clone -b 06_exception_handling clones the branch laradmin from the repository https://github.com/KodeBlog/Laradmin.git into a local folder laradmin.

Laravel Authentication Basics

In this section, we are going to use artisan to scaffold the authentication.

Run the following command

php artisan make:auth

HERE,

  • The above command will generate all of the necessary routes and controllers required for a complete authentication system.

Customizing Laravel Authentication Routes

The make:auth command modified our /routes/web.php routes and added the following line

Auth::routes();

HERE,

  • The routes(); method generates all of the required authentication routes for us.

The method routes(); is implemented in core of the framework in the file /vendor/laravel/framework/src/Illuminate/Routing/Router.php

public function auth()
{
    // 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');
    $this->post('password/email', 'Auth\ForgotPasswordController@sendResetLinkEmail');
    $this->get('password/reset/{token}', 'Auth\ResetPasswordController@showResetForm');
    $this->post('password/reset', 'Auth\ResetPasswordController@reset');
}

As you can see from the above code, the URL paths are hard-coded. This works fine for a basic application but we want our routes to be prefixed with admin.

In addition to the admin prefix, we do not want to include the register routes for admin panel users and the password reset route does not have a name.

We can change the paths in the core of the framework and our application would work just fine but what happens when you upgrade the version of the Laravel?

You will most likely have your changes overridden and have to remember which files you modified. The alternative is to write your own custom routes in /routes/web.php and leave the core file in the framework the way it is.

Replace

Auth::routes 

with

Route::group(['prefix' => 'admin','namespace' => 'Auth'],function(){
    // Authentication Routes...
    Route::get('login', 'LoginController@showLoginForm')->name('login');
    Route::post('login', 'LoginController@login');
    Route::post('logout', 'LoginController@logout')->name('logout');

    // Password Reset Routes...
    Route::get('password/reset', 'ForgotPasswordController@showLinkRequestForm')->name('password.reset');
    Route::post('password/email', 'ForgotPasswordController@sendResetLinkEmail')->name('password.email');
    Route::get('password/reset/{token}', 'ResetPasswordController@showResetForm')->name('password.reset.token');
    Route::post('password/reset', 'ResetPasswordController@reset');
});

HERE,

  • Route::group(['prefix' => 'admin','namespace' => 'Auth'],function(){…} defines a group route with the prefix of admin and namespace Auth.
  • Route::get, Route::post replicates the routes generated by artisan make:auth command with the exception of routes required to register a new user.

Note: we have also set route names for password reset routes

Customizing Laravel Authentication Views

Scaffolding generated login and password reset views. We want to be able to work with the login views from our admin template.

The authentication views are located in /resources/views/auth

Replace the contents of /resources/views/auth/login.php with the following

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <!-- Meta, title, CSS, favicons, etc. -->
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Larashop Admin Login</title>

        <!-- Bootstrap -->
        <link href="{{asset('admin/css/bootstrap.min.css')}}" rel="stylesheet">
        <!-- Font Awesome -->
        <link href="{{asset('admin/css/font-awesome.min.css')}}" rel="stylesheet">
        <!-- NProgress -->
        <link href="{{asset('admin/css/nprogress.css')}}" rel="stylesheet">
        <!-- Animate.css -->
        <link href="{{asset('admin/css/animate.min.css')}}" rel="stylesheet">

        <!-- Custom Theme Style -->
        <link href="{{asset('admin/css/custom.min.css')}}" rel="stylesheet">
    </head>

    <body class="login">
        <div>
            <div class="login_wrapper">
                <div class="animate form login_form">
                    <section class="login_content">
                        <form role="form" method="POST" action="{{ route('login') }}">
                            <h1>Larashop | Login</h1>
                            {{ csrf_field() }}
                            <div class="form-group{{ $errors->has('email') ? ' has-error' : '' }}">
                                @if ($errors->has('email'))
                                <span class="help-block"><strong>{{ $errors->first('email') }}</strong></span>
                                @endif
                                <input type="text" class="form-control" id="email" name="email" placeholder="Email"/>
                            </div>
                            <div class="form-group{{ $errors->has('password') ? ' has-error' : '' }}">
                                @if ($errors->has('password'))
                                <span class="help-block"><strong>{{ $errors->first('password') }}</strong></span>
                                @endif
                                <input type="password" class="form-control" id="password" name="password" placeholder="Password"/>
                            </div>
                            <div>
                                <button type="submit" class="btn btn-default submit">Login</button>
                                <a class="reset_pass" href="{{route('password.reset')}}">Lost your password?</a>
                            </div>

                            <div class="clearfix"></div>

                            <div class="separator">

                                <div class="clearfix"></div>
                                <br />

                                <div>
                                    <h1><i class="fa fa-paw"></i> Larashop Admin Panel</h1>
                                    <p>©2017 All Rights Reserved. Brough to you by <a href="https://tutorials.kode-blog.com" target="_blank">Kode Blog Tutorials</a></p>
                                </div>
                            </div>
                        </form>
                    </section>
                </div>
            </div>
        </div>
    </body>
</html>

HERE,

  • <form role="form" method="POST" action="{{ route('login') }}"> sets the form method to POST and the action to the path of the named route login
  • {{ csrf_field() }} adds the cross site request forgery token
  • <input type="text" class="form-control" id="email" name="email" placeholder="Email"/> defines the email field with the id and name of email
  • <input type="password" class="form-control" id="password" name="password" placeholder="Password"/> defines the password field with the id and name of password
  • <button type="submit" class="btn btn-default submit">Login</button> submits the form
  • <a class="reset_pass" href="{{route('password.reset')}}">Lost your password?</a> displays the password reset link. The password reset path uses the named route password.reset. this makes it easy to update the password reset link without changing anything in the views.

That is all we need to login in.

Let’s now look at the password views in /resources/views/auth/passwords. We have two views namely;

  • email.blade.php – used to displayed the form that asks the user for the email to use when sending the reset link.
  • reset.blade.php – used to display the form that the user will use to reset the password.

Replace the contents of email.blade.php with the following

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <!-- Meta, title, CSS, favicons, etc. -->
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Larashop Admin Login</title>

        <!-- Bootstrap -->
        <link href="{{asset('admin/css/bootstrap.min.css')}}" rel="stylesheet">
        <!-- Font Awesome -->
        <link href="{{asset('admin/css/font-awesome.min.css')}}" rel="stylesheet">
        <!-- NProgress -->
        <link href="{{asset('admin/css/nprogress.css')}}" rel="stylesheet">
        <!-- Animate.css -->
        <link href="{{asset('admin/css/animate.min.css')}}" rel="stylesheet">

        <!-- Custom Theme Style -->
        <link href="{{asset('admin/css/custom.min.css')}}" rel="stylesheet">
    </head>

    <body class="login">
        <div>
            <a class="hiddenanchor" id="signup"></a>
            <a class="hiddenanchor" id="lostpassword"></a>

            <div class="login_wrapper">
                <div class="animate form login_form">
                    <section class="login_content">
                        @if (session('status'))
                        <div class="alert alert-success">
                            {{ session('status') }}
                        </div>
                        @endif

                        <form class="form-horizontal" role="form" method="POST" action="{{ route('password.email') }}">
                            <h1>Password Reset</h1>
                            {{ csrf_field() }}

                            <div class="form-group{{ $errors->has('email') ? ' has-error' : '' }}">
                                @if ($errors->has('email'))
                                <span class="help-block"><strong>{{ $errors->first('email') }}</strong></span>
                                @endif
                                <input type="text" class="form-control" id="email" name="email" placeholder="Email"/>
                            </div>

                            <div>
                                <button type="submit" class="btn btn-default submit">Send Password Reset Link</button>
                                <a class="reset_pass" href="{{route('login')}}">Login</a>
                            </div>

                            <div class="clearfix"></div>

                            <div class="separator">

                                <div class="clearfix"></div>
                                <br />

                                <div>
                                    <h1><i class="fa fa-paw"></i> Larashop Admin Panel</h1>
                                    <p>©2017 All Rights Reserved.</p>
                                </div>
                            </div>
                        </form>
                    </section>
                </div>
            </div>
        </div>
    </body>
</html>

HERE,

  • @if (session('status')) checks if the session has status set and displays it using bootstrap alerts.
  • <form class="form-horizontal" role="form" method="POST" action="{{ route('password.email') }}"> sets the form method to POST and action to the path of the named route password.email
  • {{ csrf_field() }} adds the cross site forgery fields
  • <input type="text" class="form-control" id="email" name="email" placeholder="Email"/>adds the input box email that the use will user to enter the email address where the reset link should be sent.
  • <button type="submit" class="btn btn-default submit">Send Password Reset Link</button> submits the form.
  • <a class="reset_pass" href="{{route('login')}}">Login</a> includes a login link on the email reset page in case the user decides to go back to the login page.

Replace the contents of reset.blade.php with the following

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <!-- Meta, title, CSS, favicons, etc. -->
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <title>Larashop Admin | Password Reset</title>

        <!-- Bootstrap -->
        <link href="{{asset('admin/css/bootstrap.min.css')}}" rel="stylesheet">
        <!-- Font Awesome -->
        <link href="{{asset('admin/css/font-awesome.min.css')}}" rel="stylesheet">
        <!-- NProgress -->
        <link href="{{asset('admin/css/nprogress.css')}}" rel="stylesheet">
        <!-- Animate.css -->
        <link href="{{asset('admin/css/animate.min.css')}}" rel="stylesheet">

        <!-- Custom Theme Style -->
        <link href="{{asset('admin/css/custom.min.css')}}" rel="stylesheet">
    </head>

    <body class="login">
        <div>
            <a class="hiddenanchor" id="signup"></a>
            <a class="hiddenanchor" id="lostpassword"></a>

            <div class="login_wrapper">
                <div class="animate form login_form">
                    <section class="login_content">
                        @if (session('status'))
                        <div class="alert alert-success">
                            {{ session('status') }}
                        </div>
                        @endif
                        <form role="form" method="POST" action="{{ route('password.reset') }}">
                            <h3>Reset Password</h3>

                            {{ csrf_field() }}

                            <input type="hidden" name="token" value="{{ $token }}">

                            <div class="form-group{{ $errors->has('email') ? ' has-error' : '' }}">
                                @if ($errors->has('email'))
                                <span class="help-block"><strong>{{ $errors->first('email') }}</strong></span>
                                @endif
                                <input type="text" class="form-control" id="email" name="email" placeholder="Email"/>
                            </div>

                            <div class="form-group{{ $errors->has('password') ? ' has-error' : '' }}">
                                @if ($errors->has('password'))
                                <span class="help-block"><strong>{{ $errors->first('password') }}</strong></span>
                                @endif
                                <input type="password" class="form-control" id="password" name="password" placeholder="Password"/>
                            </div>

                            <div class="form-group{{ $errors->has('password_confirmation') ? ' has-error' : '' }}">
                                @if ($errors->has('password_confirmation'))
                                <span class="help-block"><strong>{{ $errors->first('password_confirmation') }}</strong></span>
                                @endif
                                <input type="password_confirmation" class="form-control" id="password_confirmation" name="password_confirmation" placeholder="Confirm Password"/>
                            </div>

                            <div>
                                <button type="submit" class="btn btn-default submit">Reset Password</button>
                            </div>

                            <div class="clearfix"></div>

                            <div class="separator">

                                <div class="clearfix"></div>
                                <br />

                                <div>
                                    <h1><i class="fa fa-paw"></i> Larashop Admin Panel</h1>
                                    <p>©2017 All Rights Reserved.</p>
                                </div>
                            </div>
                        </form>
                    </section>
                </div>
            </div>
        </div>
    </body>
</html>

HERE,

  • The explanation for reset.blade.php is pretty much the same as the other views that we covered earlier on.

Custom Email Reset Notification

Why do we need to customize the reset password email notification? Because the URL passet to reset password is hard coded to domain/password/reset. We want to be able to use the path domain/admin/password/reset.

We will use artisan to make our life easier. Run the following command

php artisan make:notification LarashopAdminResetPassword

the above command will create a Notifications directory in app directory. It will also create the file app/Notifications/LarashopAdminResetPassword.php

Replace the contents of LarashopAdminResetPassword.php with the following

<?php

namespace Larashop\Notifications;

use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Messages\MailMessage;

class LarashopAdminResetPassword extends Notification
{

    public function __construct($token)
    {
        $this->token = $token;
    }


    public function via($notifiable)
    {
        return ['mail'];
    }

    public function toMail($notifiable)
    {
        return (new MailMessage)
            ->line('You are receiving this email because we received a password reset request for your account.')
            ->action('Reset Password', route('password.reset.token',['token' => $this->token]))
            ->line('If you did not request a password reset, no further action is required.');
    }
}

HERE, • The method public function toMail($notifiable){...} uses the named route password.reset.token route('password.reset.token',['token' => $this->token]) to create the path for the password reset token.

Laravel sending fake SMTP emails

In this section, we will look at how to use mailtrap.io to send emails in Laravel. In a nutshell, you will create an account with mailtrap (free version with a limitation 50 mails in the inbox), and configure the email configurations. All emails sent from the app will be send to your mailtrap inbox. Mailtrap is for development purposes only.

The following screen shot shows what the mailtrap inbox looks like

Mailtrap Inbox

Visit https://mailtrap.io/ to create a free account

Once you are logged in, you should be able to see the demo mailbox Click on the settings button as shown in the image below

Mailtrap Settings

Mailtrap SMTP Settings

The settings tab will present you with the username and password that you will need to configure in mailtrap.

Open .env file

Set the following mail settings

MAIL_DRIVER=smtp
MAIL_HOST=mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=replace_with_your_username
MAIL_PASSWORD=replace_with_your_password

Default Login account

We will use tinker to create a new user account

Run the artisan commands

$user = new Larashop\Models\User
$user->name = "Rodrick Kazembe"
$user->password = bcrypt('ceasar')
$user->save()

You can now try to login using the above account details that we just created. You can also try out other features such as password reset.

Summary

In this tutorial, we have looked at how to use artisan to scaffold the complete authentication system and then customizing it to meet our needs.

What’s next?

The next tutorial will be on user roles and permissions.

Kode Blog Tutorials is dedicated to bring you update to date, high quality free tutorials. You can support us by using the social media buttons to like and share the tutorial and subscribing to our newsletter. Please use the comments section below to give us feedback.

Each week, a new Laravel tutorial is added to our collection. Subscribe to our newsletter, like our Facebook fan page or follow us on Twitter to get free updates when the collection is updated.