As we continue Implementing CRM System with Laravel in this part we will add the system login page to authenticate sales person to access using an email address and password.
Series Topics:
- Part 1: Preparation
- Part 2: Database
- Part 3: Models & Relations
- Part 4: Preparing Login Page
- Part 5: Users Module
- Part 6: Roles & Permissions
- Part 7: Documents Module
- Part 8: Contacts Module
- Part 9: Tasks Module
- Part 10: Mailbox Module
- Part 11: Mailbox Module Complete
- Part 12: Calendar Module
- Part 13: Finishing
As you may already know in building admin panels the first step is to create the login form before any other step so we will start creating the Auth routes next we will modify the system routes to be prefixed with an /admin prefix so we will add them inside route group then add a new layout for the login.
Generating Auth Scaffolding
Let’s generate the auth scaffolding, this is easy in laravel just run this command in the terminal:
php artisan make:auth
The above command will generate the auth routes and the auth views, now if you checked routes/web.php you will see that laravel append Auth::routes() this method contain the auth routes like /login, /logout, and /register.
Next after we generated the auth routes we need to make some refinements to some files to make authentication work correctly.
Open routes/web.php and modify it like shown here:
<?php /* |-------------------------------------------------------------------------- | Web Routes |-------------------------------------------------------------------------- | | Here is where you can register web routes for your application. These | routes are loaded by the RouteServiceProvider within a group which | contains the "web" middleware group. Now create something great! | */ Route::group(['prefix' => 'admin', 'middleware' => 'auth'], function () { Route::get('/', function () { return view('pages.home.index'); }); }); Route::get('/', function () { return redirect()->to('/admin'); }); Auth::routes();
In this code i enclosed the admin routes using a route group of /admin and applied the auth middleware to those routes so only the logged in users can view those pages.
Also i modified the default route “/” to redirect to the /admin route.
Add a new layout file resources/views/layout/auth.blade.php with the below code
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>Mini CRM | Log in</title> <!-- Tell the browser to be responsive to screen width --> <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport"> <!-- Bootstrap 3.3.7 --> <link rel="stylesheet" href="{{ url('theme') . '/bower_components/' }}bootstrap/dist/css/bootstrap.min.css"> <!-- Font Awesome --> <link rel="stylesheet" href="{{ url('theme') . '/bower_components/' }}bower_components/font-awesome/css/font-awesome.min.css"> <!-- Ionicons --> <link rel="stylesheet" href="{{ url('theme') . '/bower_components/' }}bower_components/Ionicons/css/ionicons.min.css"> <!-- Theme style --> <link rel="stylesheet" href="{{ url('theme') . '/dist/' }}/css/AdminLTE.min.css"> <!-- iCheck --> <link rel="stylesheet" href="{{ url('theme') . '/plugins/' }}/iCheck/square/blue.css"> <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries --> <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> <!--[if lt IE 9]> <script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script> <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> <![endif]--> <!-- Google Font --> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic"> </head> <body class="hold-transition login-page"> @yield('content') <!-- jQuery 3 --> <script src="{{ url('theme') . '/bower_components/' }}/jquery/dist/jquery.min.js"></script> <!-- Bootstrap 3.3.7 --> <script src="{{ url('theme') . '/bower_components/' }}/bootstrap/dist/js/bootstrap.min.js"></script> <!-- iCheck --> <script src="{{ url('theme') . '/plugins/' }}/iCheck/icheck.min.js"></script> <script> $(function () { $('input').iCheck({ checkboxClass: 'icheckbox_square-blue', radioClass: 'iradio_square-blue', increaseArea: '20%' /* optional */ }); }); </script> </body> </html>
Modify resources/views/auth/login.blade.php like this:
@extends('layout.auth') @section('content') <div class="login-box"> <div class="login-logo"> <b>Mini</b>CRM </div> <!-- /.login-logo --> <div class="login-box-body"> <p class="login-box-msg">Sign in to start your session</p> <form action="{{ route('login') }}" method="post" aria-label="{{ __('Login') }}"> @csrf <div class="form-group has-feedback"> <input type="email" id="email" name="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" placeholder="{{ __('E-Mail Address') }}"> <span class="glyphicon glyphicon-envelope form-control-feedback"></span> @if ($errors->has('email')) <span class="invalid-feedback" role="alert"> <strong>{{ $errors->first('email') }}</strong> </span> @endif </div> <div class="form-group has-feedback"> <input type="password" id="password" name="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" placeholder="{{ __('Password') }}"> <span class="glyphicon glyphicon-lock form-control-feedback"></span> @if ($errors->has('password')) <span class="invalid-feedback" role="alert"> <strong>{{ $errors->first('password') }}</strong> </span> @endif </div> <div class="row"> <div class="col-xs-8"> <div class="checkbox icheck"> <label> <input type="checkbox" name="remember" id="remember" {{ old('remember') ? 'checked' : '' }}> {{ __('Remember Me') }} </label> </div> </div> <!-- /.col --> <div class="col-xs-4"> <button type="submit" class="btn btn-primary btn-block btn-flat">{{ __('Login') }}</button> </div> <!-- /.col --> </div> </form> </div> <!-- /.login-box-body --> </div> <!-- /.login-box --> @endsection
As you see this login form comes already with the AdminLTE theme, i replaced the default laravel login form with this form.
Modify app/Http/Controllers/Auth/LoginController.php:
...... ...... protected $redirectTo = '/admin'; ..... ..... // override this function public function login(\Illuminate\Http\Request $request) { $this->validateLogin($request); // 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. if ($this->hasTooManyLoginAttempts($request)) { $this->fireLockoutEvent($request); return $this->sendLockoutResponse($request); } // This section is the only change if ($this->guard()->validate($this->credentials($request))) { $user = $this->guard()->getLastAttempted(); // Make sure the user is active if ($user->is_active && $this->attemptLogin($request)) { // Send the normal successful login response return $this->sendLoginResponse($request); } else { // Increment the failed login attempts and redirect back to the // login form with an error message. $this->incrementLoginAttempts($request); return redirect() ->back() ->withInput($request->only($this->username(), 'remember')) ->withErrors(['active' => 'You must be active to login.']); } } // 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. $this->incrementLoginAttempts($request); return $this->sendFailedLoginResponse($request); }
As shown above i have modified the LoginController where i override the login method to enable active users only to login “is_active“.
app/Http/Middleware/RedirectIfAuthenticated.php:
...... ...... public function handle($request, Closure $next, $guard = null) { if (Auth::guard($guard)->check()) { return redirect('/admin'); } return $next($request); } ..... .....
resources/views/layout/header.blade.php:
<header class="main-header"> <!-- Logo --> <a href="{{ url('/admin') }}" class="logo"> <!-- mini logo for sidebar mini 50x50 pixels --> <span class="logo-mini"><b>M</b>CRM</span> <!-- logo for regular state and mobile devices --> <span class="logo-lg"><b>Mini</b>CRM</span> </a> <!-- Header Navbar: style can be found in header.less --> <nav class="navbar navbar-static-top"> <!-- Sidebar toggle button--> <a href="#" class="sidebar-toggle" data-toggle="push-menu" role="button"> <span class="sr-only">Toggle navigation</span> </a> <div class="navbar-custom-menu"> <ul class="nav navbar-nav"> <!-- Messages: style can be found in dropdown.less--> <li class="dropdown messages-menu"> <a href="#" class="dropdown-toggle" data-toggle="dropdown"> <i class="fa fa-envelope-o"></i> <span class="label label-success">4</span> </a> <ul class="dropdown-menu"> <li class="header">You have 4 messages</li> <li> <!-- inner menu: contains the actual data --> <ul class="menu"> <li><!-- start message --> <a href="#"> <div class="pull-left"> <img src="{{ url('theme') . '/dist/' }}/img/user2-160x160.jpg" class="img-circle" alt="User Image"> </div> <h4> Support Team <small><i class="fa fa-clock-o"></i> 5 mins</small> </h4> <p>Why not buy a new awesome theme?</p> </a> </li> <!-- end message --> <li> <a href="#"> <div class="pull-left"> <img src="{{ url('theme') . '/dist/' }}/img/user3-128x128.jpg" class="img-circle" alt="User Image"> </div> <h4> AdminLTE Design Team <small><i class="fa fa-clock-o"></i> 2 hours</small> </h4> <p>Why not buy a new awesome theme?</p> </a> </li> <li> <a href="#"> <div class="pull-left"> <img src="{{ url('theme') . '/dist/' }}/img/user4-128x128.jpg" class="img-circle" alt="User Image"> </div> <h4> Developers <small><i class="fa fa-clock-o"></i> Today</small> </h4> <p>Why not buy a new awesome theme?</p> </a> </li> <li> <a href="#"> <div class="pull-left"> <img src="{{ url('theme') . '/dist/' }}/img/user3-128x128.jpg" class="img-circle" alt="User Image"> </div> <h4> Sales Department <small><i class="fa fa-clock-o"></i> Yesterday</small> </h4> <p>Why not buy a new awesome theme?</p> </a> </li> <li> <a href="#"> <div class="pull-left"> <img src="{{ url('theme') . '/dist/' }}/img/user4-128x128.jpg" class="img-circle" alt="User Image"> </div> <h4> Reviewers <small><i class="fa fa-clock-o"></i> 2 days</small> </h4> <p>Why not buy a new awesome theme?</p> </a> </li> </ul> </li> <li class="footer"><a href="#">See All Messages</a></li> </ul> </li> <!-- User Account: style can be found in dropdown.less --> <li class="dropdown user user-menu"> <a href="#" class="dropdown-toggle" data-toggle="dropdown"> @if(\Auth::user()->image != null) <img src="{{ url('uploads/users/' . \Auth::user()->image) }}" width="160" height="160" class="user-image" alt="User Image"> @else <img src="{{ url('theme/dist/img/image_placeholder.png') }}" class="user-image" alt="User Image"> @endif <span class="hidden-xs">{{ \Auth::user()->name }}</span> </a> <ul class="dropdown-menu"> <!-- User image --> <li class="user-header"> @if(\Auth::user()->image != null) <img src="{{ url('uploads/users/' . \Auth::user()->image) }}" width="160" height="160" class="img-circle" alt="User Image"> @else <img src="{{ url('theme/dist/img/image_placeholder.png') }}" class="img-circle" alt="User Image"> @endif <p> {{ \Auth::user()->name . (\Auth::user()->position_title!=''?' - ' . \Auth::user()->position_title:'') }} @if(\Auth::user()->created_at != null) <small>Member since {{ \Auth::user()->created_at->diffForHumans() }}</small> @endif </p> </li> <!-- Menu Footer--> <li class="user-footer"> <div class="pull-left"> <a href="#" class="btn btn-default btn-flat">Profile</a> </div> <div class="pull-right"> <a href="{{ route('logout') }}" onclick="event.preventDefault(); document.getElementById('logout-form').submit();" class="btn btn-default btn-flat">Sign out</a> <form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;"> @csrf </form> </div> </li> </ul> </li> </ul> </div> </nav> </header>
resources/views/layout/sidebar.blade.php:
<!-- Left side column. contains the logo and sidebar --> <aside class="main-sidebar"> <!-- sidebar: style can be found in sidebar.less --> <section class="sidebar"> <!-- Sidebar user panel --> <div class="user-panel"> <div class="pull-left image"> @if(\Auth::user()->image != null) <img src="{{ url('uploads/users/' . \Auth::user()->image) }}" class="img-circle" alt="User Image"> @else <img src="{{ url('theme/dist/img/image_placeholder.png') }}" class="img-circle" alt="User Image"> @endif </div> <div class="pull-left info"> <p>{{ \Auth::user()->name }}</p> </div> </div> <!-- sidebar menu: : style can be found in sidebar.less --> <ul class="sidebar-menu" data-widget="tree"> <li class="header">MAIN NAVIGATION</li> <li class="active treeview"> <a href="#"> <i class="fa fa-dashboard"></i> <span>Dashboard</span> <span class="pull-right-container"> <i class="fa fa-angle-left pull-right"></i> </span> </a> <ul class="treeview-menu"> <li class="active"><a href="#"><i class="fa fa-circle-o"></i> Home</a></li> </ul> </li> </ul> </section> <!-- /.sidebar --> </aside>
I updated the above files to display the correct username based on the currently logged in user also i displayed a placeholder image if the user has no image, you can download the placeholder image from here and place it inside public/theme/dist/img folder. Finally i added the logout link and logout form in.
Don’t forget to create an uploads/ directory inside of public/ directory with a writable permissions similar to this structure:
Now try to navigate to http://localhost/crm/public it will show you the login page, now login using email: admin@my_crm.com and password: admin.
Administrator Middleware
We will add a new middleware to allow or disallow certain users from accessing certain areas bases on if the user is super admin “is_admin=1” or it has a permission to access that area.
php artisan make:middleware Administrator
app/Http/Middleware/Administrator.php
<?php namespace App\Http\Middleware; use Closure; use Illuminate\Contracts\Auth\Guard; class Administrator { protected $auth; public function __construct(Guard $auth) { $this->auth = $auth; } /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next, $permission = null) { if($this->auth->user()->is_admin == 1) { return $next($request); } else { if($permission != null && ($permission_parts = explode("|", $permission))) { $action_permission_array = array_filter($permission_parts, function ($value) use ($request) { return ($parts = explode("-", $value)) && $parts[0] == $request->route()->getActionMethod(); }); if($action_permission_array && count($action_permission_array)) { $action_permission = array_pop($action_permission_array); $parts = explode("-", $action_permission); if(isset($parts[1]) && $this->auth->user()->can($parts[1])) { return $next($request); } } } } return redirect('/admin/forbidden'); } }
As shown i checked for the current user is_admin property, if it equal to 1 then it’s allowed, otherwise we check if the user has a permission for the current route action, as you see i send a parameter to the function called permission, this parameter will be passed inside each controller you want to authorize access to it.
app/Http/Kernel.php
..... ..... 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, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'admin' => \App\Http\Middleware\Administrator::class // administrator middleware ]; ..... .....
I have added the middleware to the laravel kernel $routeMiddleware property with alias “admin“.
routes/web.php
Route::group(['prefix' => 'admin', 'middleware' => 'auth'], function () { Route::get('/', function () { return view('pages.home.index'); }); Route::get('/forbidden', function () { return view('pages.forbidden.forbidden_area'); }); }); Route::get('/', function () { return redirect()->to('/admin'); }); Auth::routes();
resources/views/pages/forbidden/forbidden_area.blade.php
@extends('layout.app') @section('title', ' | Forbidden') @section('content') <!-- Content Header (Page header) --> <section class="content-header"> <h1> Forbidden area </h1> </section> <section class="content"> <div class="row"> <div class="col-md-12"> <p>Trying to access forbidden area</p> </div> </div> </section> @stop
Continue to Part 5: Beginning the Users Module>>>
so i’m triying to follow your tutorial, i’m new in laravel, has been a while since i don’t code anithing (2006). A lot of things changed since then, and a lot of thing change since you did this tutorial. I want to get this crm up and running but i keep finding obstacles because of the out dated versions. If you have an up to date tutorial like this or know where i can find one that is up to date i will apreciate that.
You still can use the old version of laravel and the old packages with no problem.