DEV Community

Cover image for Authenticating a React App with Laravel Sanctum - Part 1
Dog Smile Factory
Dog Smile Factory

Posted on • Edited on • Originally published at bob-humphrey.com

Authenticating a React App with Laravel Sanctum - Part 1

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 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 and view the complete code for the React client application and the Laravel server application.

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.

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:

use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;

and

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

as shown below:

<?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 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.

'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.

'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.

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

Then set support_credentials to true.

'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

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.

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.

Top comments (6)

Collapse
 
coopercodes profile image
🧸 🏳️‍🌈 cooper-codes 💻 🎮

Hi, great article, this is really helping me demistify a lot of the process (still new to Laravel).
Just wanted to let you know, if you are on Laravel 8+ that tinker command wont work to create a user, but i found the solution and thought I'd leave it here for others that may get stuck.

User::factory()->create(['email'=>'bill@gmail.com', 'name'=>'Bill', 'password'=> bcrypt('password')]);
Enter fullscreen mode Exit fullscreen mode
Collapse
 
webprogrammerio profile image
webprogrammerio

Yes, I found the same thing with Laravel 9. Luckily I found your solution

Collapse
 
santiagazo profile image
Jay L • Edited

Bob, awesome tut. Thank you so much. I've gone through a few of these laravel/sanctum SPA auth tuts and I keep running into the same problem. I can create the user and log in but anytime I try to hit the middleware route (in this case api/user) I get the response "unauthenticated".

I used your gitLab examples to ensure I had everything working correctly. I've updated the CORS file with allowed_origins => ['*'], I have added to my .env the following two lines:

SESSION_DOMAIN=.lionenergy.com
SANCTUM_STATEFUL_DOMAINS=app.lionenergy.com/

They appear to be working correctly since I can login and get a successful message back. The referee (sanctum_stateful_domains) is exactly what's shown in my chrome developer tools.

I am getting back the x-xsrf-token and it shows that it's getting sent in the request headers to the api/user route too (see image)

Nonetheless, unauthenticated is what I get returned. I'm missing something. Every time I've done this, I've ended up here. Any ideas? Is there something I have wrong with my server-side code?

Collapse
 
wmdarrigo profile image
William D'Arrigo

I have the same issue. Found anything?

Collapse
 
malitov profile image
Pavlo Malitov

Cool! Thanks.
Waiting for the second part :)

Collapse
 
yornletard profile image
Yornletard

You'll find an answer here:
insidert.medium.com/fixing-unauthe...