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)
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.
Yes, I found the same thing with Laravel 9. Luckily I found your solution
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?
I have the same issue. Found anything?
Cool! Thanks.
Waiting for the second part :)
You'll find an answer here:
insidert.medium.com/fixing-unauthe...