Authenticating a React App with Laravel Sanctum - Part 1
Tags: laravel, react, tutorial, security
Published: 2020-04-08 17:17:47



My tech stack of choice for building web applications is React on the front end and Laravel on the back. One of the challenges of this approach involves authenticating the user so that database resources are only available to authorized individuals. This task is a lot more straightforward now that the [Sanctum](https://laravel.com/docs/7.x/sanctum) package has been added to Laravel 7.

To show how this works, I've created a simple application made up of three parts

- the user signs up for access
- the user logs in
- the user logs out

You can try it out [here](https://auth.bob-humphrey.com) and view the complete code for the [React client application](https://gitlab.com/Bob_Humphrey/laravel-auth) and the [Laravel server application](https://gitlab.com/Bob_Humphrey/react-auth).

## Laravel with Sanctum

Laravel Sanctum handles all the work of authenticating your users. However, there are a lot of little details to get this set up. Just take them one at a time, don't miss any steps, and you'll have your application working perfectly in very short order.

This guide assumes that you have a basic familiarity with setting up and running a Laravel application, including using the command line and running Laravel artisan commands.

### Database

First, you're going to need a database for saving your information. I used MySQL for this application and created a database named **auth**.

### Install Laravel

Then I created my Laravel app, using Composer:

```
composer create-project --prefer-dist laravel/laravel APP_NAME
```

There are detailed instructions for starting a new project on [the Laravel site](https://laravel.com/docs/7.x#installing-laravel).

Edit the **.env** file to update the application name and your database information.

```
APP_NAME=Laravel_Sanctum_Authentication
APP_ENV=local
APP_KEY=base64:XXXXXXX
APP_DEBUG=true
APP_URL=http://localhost

LOG_CHANNEL=stack

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=auth
DB_USERNAME=XXXXXXX
DB_PASSWORD=XXXXXXX
```

### Install and Configure Sanctum

CD into the application directory and add Sanctum to the project.

```
composer require laravel/sanctum
```

Next, create a Sanctum configuration file and the database tables.

```
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"

php artisan migrate
```

Update **app/Http/Kernel.php** to add the Sanctum middleware to the API middleware group.

Add the following lines of code:

```php
use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;
```

and

```php
'api' => [
    EnsureFrontendRequestsAreStateful::class,
    'throttle:60,1',
    \Illuminate\Routing\Middleware\SubstituteBindings::class,
],
```

as shown below:

```php
<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;
use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;

class Kernel extends HttpKernel
{
    /**
     * The application's global HTTP middleware stack.
     *
     * These middleware are run during every request to your application.
     *
     * @var array
     */
    protected $middleware = [
        \App\Http\Middleware\TrustProxies::class,
        \Fruitcake\Cors\HandleCors::class,
        \App\Http\Middleware\CheckForMaintenanceMode::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
    ];

    /**
     * The application's route middleware groups.
     *
     * @var array
     */
    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            EnsureFrontendRequestsAreStateful::class,
            'throttle:60,1',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];
```

### Configure CORS

We need to setup [Cross-Origin Resource Sharing](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) so that requests to our API are rejected, except when they come from our front end React application.

Make the following changes to **config/cors.php**.

```php
'paths' => ['api/*', 'sanctum/csrf-cookie'],
    'allowed_methods' => ['*'],
    'allowed_origins' => ['https://auth.bob-humphrey.com', 'http://localhost:3000'],
    //'allowed_origins' => ['*'],
    'allowed_origins_patterns' => [],
    'allowed_headers' => ['*'],
    'exposed_headers' => [],
    'max_age' => 0,
    'supports_credentials' => true,
```

Modify **paths** to indicate which endpoints need to be protected: in this case **api/\*** and **sanctum/csrf-cookie**.

```php
'paths' => ['api/*', 'sanctum/csrf-cookie'],
```

Modify **allowed-origins** to specify the urls from which requests will be accepted. This will be the production and development urls of your React app, **https://auth.bob-humphrey.com** (for my app) and **http://localhost:3000**.

```php
'allowed_origins' => ['https://auth.bob-humphrey.com', 'http://localhost:3000'],
```

Then set **support_credentials** to **true**.

```php
'supports_credentials' => true,
```

### User Controller

Next, create the User controller.

```
php artisan make:controller UserController
```

Edit **app/Http/Controllers/UserController** so that it looks like this.

```php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use App\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;

class UserController extends Controller
{
    public function register(Request $request)
    {
        $this->validator($request->all())->validate();
        $user = $this->create($request->all());
        $this->guard()->login($user);
        return response()->json([
            'user' => $user,
            'message' => 'registration successful'
        ], 200);
    }
    /**
     * 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', 'string', 'max:255'],
            'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
            //'password' => ['required', 'string', 'min:4', 'confirmed'],
            // NO PASSWORD CONFIRMATION
            'password' => ['required', 'string', 'min:4'],
        ]);
    }

    /**
     * Create a new user instance after a valid registration.
     *
     * @param  array  $data
     * @return \App\User
     */
    protected function create(array $data)
    {
        return User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => Hash::make($data['password']),
        ]);
    }
    protected function guard()
    {
        return Auth::guard();
    }

    public function login(Request $request)
    {
        $credentials = $request->only('email', 'password');

        if (Auth::attempt($credentials)) {
            // Authentication passed...
            $authuser = auth()->user();
            return response()->json(['message' => 'Login successful'], 200);
        } else {
            return response()->json(['message' => 'Invalid email or password'], 401);
        }
    }

    public function logout()
    {
        Auth::logout();
        return response()->json(['message' => 'Logged Out'], 200);
    }
}
```

The controller contains the register, login, and logout methods that will be called by our front end. It also contains a validator method to validate the data and a create method to add a new user to the database.

### API Routes

Now we update **routes/api** as follows.

```php
Route::middleware('auth:sanctum')->get('/user', function (Request $request) {
    return $request->user();
});

Route::post('/login', 'UserController@login');
Route::post('/register', 'UserController@register');
Route::get('/logout', 'UserController@logout');
```

The **/user** route is modified to make use of the Sanctum middleware we just installed. The front end app will not be able to get a successful response from this endpoint unless the user has first authenticated. If we were building a full blown API, all of the API routes would be protected with the Sanctum middleware.

We have also added three new endpoints to provide access to the login, register, and logout functions. Please note that all endpoints in the routes/api.php file will be prefixed with "/api". Therefore, the endpoint for the login route is "/api/login", the endpoint for the register route is "/api/register", and so forth.

### Add a New User for Testing

At this point Laravel is completely set up to handle user authentication. Let's add a new user to the database so that we can test our setup. We can use tinker to accomplish this.

```
php artisan tinker

factory(App\User::class)->create(['email'=>'bill@gmail.com','name'=>'Bill', 'password'=> bcrypt('bill')]);

exit     (to leave tinker)
```

### Part 2

The back end is finished and we're now ready to build the front end. In part two, we will walk through the tasks required to create a React app, with forms for user registration, login, and logout.