Laravel Tutorials - How to make Laravel Authentication

How to make Laravel Authentication

2020-03-08 Lukas Markevičius
How to make Laravel Authentication featured image

If you have a custom built blog or a portfolio or anything that can create a dynamic content you probably want to restrict access to who can edit your information. Or in other cases you need to create users who can have their profile, purchase something from your store or leave a comment. For these scenarios, you need to have an authentication system where only logged in users can do specific tasks on your platform. So today, if you are going to follow this Laravel authentication tutorial you will learn how to create Laravel 5.6 users and their authorization. You will learn:

First of all, what we need is a basic Laravel project with some models. If you already know how to create one that's great, if you don't you can read my other tutorial on How to create Step by Step Laravel Project and then come back. If you want to get a source code for a starting point you can get it from here. Run in terminal this command to checkout to a starting point:

$ git checkout session-messages -b <your-branch-name>

How to edit User model

You should now have a Laravel Project with Post model where you can add, edit or delete posts. Laravel automatically creates migration files for the users, so if you followed along with my tutorial you should have migrated the database and also created tables for the users. The basic User model has these columns:

  • ID
  • Name
  • Email
  • Password
  • Remember me Token
  • Timestamps

If you are fine with these columns you can skip this paragraph. If not, I will show you how to add an additional column to the users table. If you haven't migrated database you can just simply add another line to your migration file specifying new column's name and type. For this example, we will add a phone column. You can find all of your migration files in database/migrations folder and the file you are looking for probably looks something like this 2014_10_12_000000_create_users_table.php. In this file you can specify all the columns you want to have:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('users', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('email')->unique();
            $table->string('phone');
            $table->string('password');
            $table->rememberToken();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('users');
    }
}

After you have done this you can run php artisan migrate command in your terminal and you now have created a users table with additional phone column. If you migrated database before and want to add an additional column you can rollback migration with php artisan migrate:rollback command and then change your user migration file and migrate it then. Doing so, you will lose all the data stored in the database. If you do not want to lose any information you can create a new migration file and add a new column there. In your terminal run this command:

$ php artisan make:migration add_phone_column_to_users_table --table=users​

We now have created a new migration file and added a --table option which specifies to which table do you want to add changes. Now in your new file, you can add phone column:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class AddPhoneColumnToUsersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('users', function (Blueprint $table) {
          $table->string('phone')->after('email')->nullable();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
          $table->dropColumn('phone');
        });
    }
}
​

 
We added a modifier after which specifies after which column to put your new column. In the down() function we wrote to drop column phone. This way when we want to go back to the previous state we could use php artisan migrate:rollback command and it will only revert changes for the phone column instead of all users table.
 
Now we can run php artisan migrate. If you look at your database's users table you could see that we now have a phone column after email:

We now need to change a little bit User model which can be found in app/User.php:

<?php

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'phone', 'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];
}

We added to the fillable array a phone field. If we specify table columns like this then we could use the mass assignment to create users or update them.

How to make auth routes and views

Now we need to create authentication routes, controllers and views. Lucky for us, Laravel has a quick way of doing this by running php artisan make:auth command in our terminal. You should now have all the necessary files to register and login your user. Because we changed our index.blade.php file we don't have our links to register or login pages so we need to create them manually:

<!doctype html>
<html lang="{{ app()->getLocale() }}">
  <head>
      <meta charset="utf-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1">

      <title>{{ config('app.name') }}</title>

      <script src="{{ asset('js/app.js') }}" defer></script>

      <!-- Fonts -->
      <link href="https://fonts.googleapis.com/css?family=Nunito:200,600" rel="stylesheet" type="text/css">

      <link rel="stylesheet" href="{{ asset('css/app.css') }}">
  </head>

  <body>
      <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
          <a class="navbar-brand" href="{{ route('post.index') }}">{{ config('app.name') }}</a>
          <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
              aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
              <span class="navbar-toggler-icon"></span>
          </button>

          <div class="collapse navbar-collapse" id="navbarSupportedContent">
              <ul class="navbar-nav mr-auto">
                  <li class="nav-item active">
                      <a class="nav-link" href="{{ route('post.index') }}">Home <span class="sr-only">(current)</span></a>
                  </li>
              </ul>

              <ul class="navbar-nav ml-auto">
                @guest
                    <li class="nav-item">
                        <a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
                    </li>
                    <li class="nav-item">
                        <a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
                    </li>
                @else
                    <li class="nav-item">
                        <a href="{{ route('post.create') }}" class="btn btn-success my-2 my-sm-0">Create Post</a>
                    </li>
                    <li class="nav-item dropdown">
                        <a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
                            {{ Auth::user()->name }} <span class="caret"></span>
                        </a>

                        <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
                            <a class="dropdown-item" href="{{ route('logout') }}"
                                onclick="event.preventDefault();
                                                document.getElementById('logout-form').submit();">
                                {{ __('Logout') }}
                            </a>

                            <form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
                                @csrf
                            </form>
                        </div>
                    </li>
                @endguest
            </ul>
              
          </div>
      </nav>

      @if (Session::has('success'))
            <div class="alert alert-success alert-dismissible fade show" role="alert">
                <h4 class="alert-heading">Success!</h4>
                <p>{{ Session::get('success') }}</p>

                <button type="button" class="close" data-dismiss="alert aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
        @endif

        @if (Session::has('errors'))
            <div class="alert alert-danger alert-dismissible fade show" role="alert">
                <h4 class="alert-heading">Error!</h4>
                <p>
                    <ul>
                        @foreach ($errors->all() as $error)
                            <li>{{ $error }}</li>
                        @endforeach
                    </ul>
                </p>

                <button type="button" class="close" data-dismiss="alert" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
        @endif

      <div class="container py-3">
          <div class="row">
              @foreach($posts as $post)
              <div class="col-md-4">
                  <div class="card">
                      <div class="card-header">
                          <h3>{{ $post->title }}</h3>
                      </div>
                      <div class="card-body">
                          <p>{{ substr($post->description, 0, 100) }}</p>
                          <a href="{{ route('post.show', $post->slug) }}" class="btn btn-primary btn-block">Read More</a>
                      </div>
                  </div>
              </div>
              @endforeach
          </div>
      </div>
  </body>

</html>

We now have two additional links in our navbar. If we clicked on register we would be redirected to that page:

Screenshot for post index view

When we ran php artisan make:auth command Laravel also created a home view and home controller. Whenever we register, login or logout the Laravel would redirect us back to the home view which in this example we don't want to. So we will learn how to change those redirect URLs. In our app/Http/Controllers/Auth folder we have authentication controllers which are responsible for user login, register and logout functions. Redirect URL is also defined in those controllers. So now we need to open and edit LoginController.php, RegisterController.php and ResetPasswordController.php files and change protected $redirectTo variable to the desired URL we want the user to be redirected. In our case, we will change to '/' which means it will redirect to our main index view

protected $redirectTo = '/';

Because all the authentication views were created automatically by Laravel, in the registration form we don't have our newly created phone field. And that's what we will do now. In our resources/views/auth folder we need to edit register.blade.php file and add a new input field:

@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">{{ __('Register') }}</div>

                <div class="card-body">
                    <form method="POST" action="{{ route('register') }}" aria-label="{{ __('Register') }}">
                        @csrf

                        <div class="form-group row">
                            <label for="name" class="col-md-4 col-form-label text-md-right">{{ __('Name') }}</label>

                            <div class="col-md-6">
                                <input id="name" type="text" class="form-control{{ $errors->has('name') ? ' is-invalid' : '' }}" name="name" value="{{ old('name') }}" required autofocus>

                                @if ($errors->has('name'))
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $errors->first('name') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="email" class="col-md-4 col-form-label text-md-right">{{ __('E-Mail Address') }}</label>

                            <div class="col-md-6">
                                <input id="email" type="email" class="form-control{{ $errors->has('email') ? ' is-invalid' : '' }}" name="email" value="{{ old('email') }}" required>

                                @if ($errors->has('email'))
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $errors->first('email') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="phone" class="col-md-4 col-form-label text-md-right">{{ __('Phone Number') }}</label>

                            <div class="col-md-6">
                                <input id="phone" type="number" class="form-control{{ $errors->has('phone') ? ' is-invalid' : '' }}" name="phone" value="{{ old('phone') }}">

                                @if ($errors->has('phone'))
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $errors->first('phone') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="password" class="col-md-4 col-form-label text-md-right">{{ __('Password') }}</label>

                            <div class="col-md-6">
                                <input id="password" type="password" class="form-control{{ $errors->has('password') ? ' is-invalid' : '' }}" name="password" required>

                                @if ($errors->has('password'))
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $errors->first('password') }}</strong>
                                    </span>
                                @endif
                            </div>
                        </div>

                        <div class="form-group row">
                            <label for="password-confirm" class="col-md-4 col-form-label text-md-right">{{ __('Confirm Password') }}</label>

                            <div class="col-md-6">
                                <input id="password-confirm" type="password" class="form-control" name="password_confirmation" required>
                            </div>
                        </div>

                        <div class="form-group row mb-0">
                            <div class="col-md-6 offset-md-4">
                                <button type="submit" class="btn btn-primary">
                                    {{ __('Register') }}
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

We now need to change registration controller so that we could validate and add a phone to the database. In app/Http/Controllers/Auth folder open RegisterController.php file and in validator function write this:

...
protected function validator(array $data)
{
        return Validator::make($data, [
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users',
            'phone' => 'sometimes|nullable|numeric',
            'password' => 'required|string|min:6|confirmed',
        ]);
}
...

This line will make sure that phone field is required and is a number. Now in create function add a new line to add a phone to the user:

...
protected function create(array $data)
{
        return User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'phone' => $data['phone'],
            'password' => Hash::make($data['password']),
        ]);
}
...

And do you remember when we added a phone value to the fillable array in our User model? Because of that we can mass assign values to the user just like in this create function.
 
If you followed every step of this tutorial, you should now be able to register users and login.

How to protect routes with middleware

So now, you have a basic Laravel authentication system. But what's the point if you can't do anything cool with it, right? We will do that only registered users could create, edit or delete posts. One way we could do that is by setting auth middleware in our routes/web.php file:

Route::get('/', 'PostController@index')->name('index');

Route::middleware('auth')->group(function() {
  Route::get('/post/create', 'PostController@create')->name('post.create');
  Route::post('/post/create', 'PostController@store')->name('post.store');
  Route::get('/post/{slug}/edit', 'PostController@edit')->name('post.edit');
  Route::post('/post/{slug}/edit', 'PostController@update')->name('post.update');
  Route::post('/post/{slug}/destroy', 'PostController@destroy')->name('post.destroy');
});

Route::get('/post/{slug}', 'PostController@show')->name('post.show');

Auth::routes();

Route::get('/home', 'HomeController@index')->name('home');

We would set a middleware auth for the group of routes which we want to give access only to registered users. The other way is to set middleware in our PostController.php file:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Str;
use App\Post;
use Image;
use Storage;
use Session;
use Auth;

class PostController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */

     public function __construct()
     {
         $this->middleware('auth')->except(['index', 'show']);
     }
...

This way we specify that for all controllers' functions we add auth middleware except for the index and show functions which in our case can be viewed by a guest.

How to show specific actions only to logged in users

Now we need to hide certain links to the guest users so that they wouldn't have false intentions. We already have done it in index.blade.php file by writing Blade syntax @guest. This method will check if the user is logged out and then display login and register links. If not then he won't see those links and instead he will see 'Create Post' button.
 
Now we need to use the same method for post.show view to hide edit and delete buttons:

...
<div class="row">
    <div class="col-md-8 offset-md-2">
        <div class="card">
            <div class="card-header">
                <h1>{{ $post->title }}</h1>
            </div>

            <div class="card-body">
                @if ($post->image)
                    <img src="{{ asset('storage/images/' . $post->image) }}" alt="{{ $post->title }} photo" class="img-fluid">
                @endif

                <p>{{ $post->description }}</p>

                @auth
                    <a href="{{ route('post.edit', $post->slug) }}" class="btn btn-primary">Edit Post</a>
                    <form action="{{ route('post.destroy', $post->slug) }}" method="post">
                        @csrf
                        @method('DELETE')
                        <button type="submit" class="btn btn-danger mt-3">Delete</button>
                    </form>
                @endauth
            </div>
        </div>
    </div>
</div>
...


Now if the user is logged out he can't see any of edit, delete or create buttons. And if he is trying to go to the certain page typing the URL manually he will be redirected to the login page because those pages are protected by the middleware.

How to let the user edit only his own posts

You now have made user authentication where only registered users can create, edit or view posts. In this example, we are creating users for the blog which means that only user who created blog post should be able to edit or delete it. But for now, any user can edit or delete any post. This is not that we want, right? So, let's fix this.
 
First of all, what we want to do is create a new column in the posts table to store users id. We can do that by creating a new migration file. So run this command:

$ php artisan make:migration add_user_id_to_posts --table=posts


In the new migration file add user_id column:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class AddUserIdToPosts extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('posts', function (Blueprint $table) {
          $table->integer('user_id')->unsigned()->after('id');

          $table->foreign('user_id')->references('id')->on('users');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table('posts', function (Blueprint $table) {
          $table->dropForeign(['user_id']);
          $table->dropColumn('user_id');
        });
    }
}


Now run php artisan migrate command. In your database you should have a new column named user_id. If you already have posts you can update the user_id column to your user's id manually. Now what we need to do is define relationships between User and Post model. So, in app/User.php file:

<?php

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name', 'email', 'phone', 'password',
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
        'password', 'remember_token',
    ];

    public function posts() {
      return $this->hasMany('App\Post');
    }
}


And in your app/Post.php file:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    protected $fillable = ['user_id', 'title', 'slug', 'description'];

    public function getRouteKeyName()
    {
        return 'slug';
    }

    public function user() {
        return $this->belongsTo('App\User');
    }
}

You now have defined relationships between these two models. The logic behind it is that user can have many posts so we define this relationship and point it to the Post model. On the other hand, the posts can have only one user in this example. So, we define belongs to relationship and point to the User model.
 
Now we need to update our post controller so that when the user creates the post it automatically assigns his id and to check if we are the post's author when we are editing it because the user can type in manually URL to edit not his own post. So, we need to prevent this from happening.

...
    public function store(Request $request)
    {
      $validatedData = $this->validate($request, [
        'title'         => 'required|min:3|max:255',
        'slug'          => 'required|min:3|max:255|unique:posts',
        'image'         => 'sometimes|image',
        'description'   => 'required|min:3'
      ]);

      $validatedData['user_id'] = Auth::id();
      $validatedData['slug'] = Str::slug($validatedData['slug'], '-');

      $post = Post::create($validatedData);

      if ($request->hasfile('image')) {
        $image = $request->file('image');
        $filename = time() . '.' . $image->getClientOriginalExtension();
        $location = storage_path('app/public/images/') . $filename;

        Image::make($image)->save($location);

        $post->image = $filename;
        $post->save();
      }

      Session::flash('success', 'You have successfully created a post!');
      
      return redirect()->route('post.index');
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show(Post $post)
    {
      return view('post.show')->withPost($post);
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit(Post $post)
    {
      if ($post->user_id != Auth::id()) {
        return redirect()->back();
      }
      return view('post.edit')->withPost($post);
    }
...


Now, all we need to do is edit show.blade.php file so that it wouldn't show the edit and delete posts to those who are not the post's author.

...
<div class="row">
    <div class="col-md-8 offset-md-2">
        <div class="card">
            <div class="card-header">
                <h1>{{ $post->title }}</h1>
            </div>

            <div class="card-body">
                @if ($post->image)
                    <img src="{{ asset('storage/images/' . $post->image) }}" alt="{{ $post->title }} photo" class="img-fluid">
                @endif

                <p>{{ $post->description }}</p>

                @if (Auth::id() == $post->user_id)
                    <a href="{{ route('post.edit', $post->slug) }}" class="btn btn-primary">Edit Post</a>
                    <form action="{{ route('post.destroy', $post->slug) }}" method="post">
                        @csrf
                        @method('DELETE')
                        <button type="submit" class="btn btn-danger mt-3">Delete</button>
                    </form>
                @endif
            </div>
        </div>
    </div>
</div>
...


And that's it! You have completed a Laravel authentication tutorial! You now have created a user authentication system where they can register and login and only registered users can create posts. The next thing you have learned is to make sure that only the post's author could edit or delete his post. If you wish to get a complete source code you can get it from here. If you want to clone this repository locally don't forget to checkout to user-authentication tag by running command:

$ git checkout user-authentication -b <your-branch-name>

If you have any issues or it was hard to follow along, feel free to write down your questions in the comments.

How to make Laravel Authentication pinterest image

Related Tutorials

How to Generate a Simple XML Sitemap using Laravel

Posted 2020-03-08 Lukas Markevičius

Today we are going to learn how to generate a simple XML sitemap using Laravel. Mainly you can create it either manually or create some sort of automated solution. To create it manually you can make y...

How to Create Slugs in Laravel

Posted 2020-03-27 Lukas Markevičius

Today we are going to learn how to create slugs in Laravel. You are going to learn: Why use slugs How to create slugs How to show objects with the slugs How to edit slugs Why use Slugs If yo...

Back