How To Authenticate APIs With Laravel Sanctum

How To Authenticate APIs With Laravel Sanctum

Given Ncube

When building web applications it is often a good idea to build an external API for other frontend clients like mobile apps or something. In this article, I'm going to show you how to secure your Laravel APIs with Sanctum. I assume you have a basic knowledge of Laravel and REST APIs. You will need :

Create a new Laravel project and set up a database connection in your .env file. You'll also need to install laravel/ui

composer require laravel/ui

Then run the auth scaffold

php artisan ui bootstrap --auth

After this, you should have a working auth scaffold. For this example, I'm creating a simple todos API.

Step 1: Models and Controllers

Let's create our models

php artisan make:model Todo -a

This will create the Todos model, a migration file a seeder, a controller, and a factory. Open the migration file and add the following code.


public function up()
{
    Schema::create('todos', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->boolean('complete')->default(false);
        $table->timestamps();
    });
}


Step 2: setup sanctum

Our models and controller are now in place let's set up our authentication with sanctum. Install Laravel Sanctum.

composer require laravel/sanctum

Now run the migration

php artisan migrate

To begin issuing tokens for users, your User model should use the HasApiTokens trait from sanctum:

use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasFactory, Notifiable, HasApiTokens;


Now we can start protecting our routes with sanctum which leads us to the next step

Step 4: Routes

Open the RouteServiceProvider and uncomment the $namespace property like this:

protected $namespace = 'App\\Http\\Controllers';    

Now we're set let's add our API routes. In the api routes file add the following routes.


Route::post('/login', 'API\UserController@login');
Route::middleware('auth:sanctum')->group(function () {
    Route::delete('/logout', 'API\UserController@logout');
    Route::resource('todos', 'TodosController');
});


As you can see we don't have a UserController in the API namespace, let's create it

php artisan make:controller API\\UserController

We're almost done

Step 4: Issuing Tokens

In the API\Usercontroller add the following code



namespace App\Http\Controllers\API;

use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Hash;
use Illuminate\Http\Request;
use App\Models\User;

class UserController extends Controller
{
   /**
    * Issues an auth token to the user
    *
    * @return \Illuminate\Http\Response
    */
   public function login(Request $request)
   {
       $request->validate([
           'email' => 'required|email',
           'password' => 'required',
           'device_name' => 'required',
       ]);

       $user = User::where('email', $request->email)->first();

       if (! $user || ! Hash::check($request->password, $user->password)) {
           throw ValidationException::withMessages([
               'email' => ['The provided credentials are incorrect.'],
           ]);
       }

       return response()->json([
           'token' => $user->createToken($request->device_name)->plainTextToken,
       ]);
   }

   /**
    * Revokes the user's access token
    *
    * @return \Illuminate\Http\Response
    */
   public function logout(Request $request)
   {
       $request->user()->currentAccessToken()->delete();
       return response()->noContent();
   }
}


The way this API works is that your users create accounts from the main website then login with the API client to get a token for that specific client. The login method takes an email, password, and device name. If the user's credentials are verified the API returns the token as JSON to the client which is specific to that device. The logout method simply revokes the current token nothing fancy there. Our API auth is done, let's add some JSON resources for our todos.

php artisan make:resource TodoResource

In the todo controller let's return some JSON. We're only going to implement the index action.

/**
 * Display a listing of the resource.
 *
 * @return \Illuminate\Http\Response
 */
public function index()
{
	$todos = Todo::all();
  	return new TodoResource($todos);
}
 

Create a factory and seeder for dummy data and seed the database

Testing our auth

I'm using curl to test it you can use Postman or Insomnia or whatever

┌──(given㉿flixtechs)-[~/Documents/PHP/api]
└─$ curl  "http://127.0.0.1:8000/api/todos" -H 'Accept: application/json'
{"message":"Unauthenticated."}  


As you can see I'm not authenticated I can't access the resource. Let's try to log in. I've already created a user via the main web UI.

┌──(given㉿flixtechs)-[~/Documents/PHP/api]
└─$ curl  'http://127.0.0.1:8000/api/login' -H 'Accept: application\json' -X POST -d email='giveny12@yahoo.com' -d password='fY@!4U@MYWcf.cz' -d device_name='curl-client'
{"token":"9|XArCVpkPTpGoax4aF5KDQnlyFNWD8SJ0pkUTdDxD"}    

As you can see, I've logged in and received a token in your client you can save this token somewhere for future requests. To authenticate with this token we have to use it as Bearer in the Authorization header:

┌──(given㉿flixtechs)-[~/Documents/PHP/api]
└─$ curl http://127.0.0.1:8000/api/todos \
> -H 'Accept: application/json' \
> -H 'Authorization: Bearer 9|XArCVpkPTpGoax4aF5KDQnlyFNWD8SJ0pkUTdDxD'
{"data":[{"id":1,"name":"Kelley Gottlieb","complete":0,"created_at":"2020-11-29T12:47:09.000000Z","updated_at":"2020-11-29T12:47:09.000000Z"},{"id":2,"name":"Lazaro Goldner","complete":0,"created_at":"2020-11-29T12:47:09.000000Z","updated_at":"2020-11-29T12:47:09.000000Z"},{"id":3,"name":"Mr. Aaron Bruen","complete":0,"created_at":"2020-11-29T12:47:09.000000Z","updated_at":"2020-11-29T12:47:09.000000Z"},{"id":4,"name":"Kyla Marquardt","complete":0,"created_at":"2020-11-29T12:47:10.000000Z","updated_at":"2020-11-29T12:47:10.000000Z"},{"id":5,"name":"Lia Bergnaum","complete":0,"created_at":"2020-11-29T12:47:10.000000Z","updated_at":"2020-11-29T12:47:10.000000Z"},{"id":6,"name":"Robbie Brekke","complete":0,"created_at":"2020-11-29T12:47:10.000000Z","updated_at":"2020-11-29T12:47:10.000000Z"},{"id":7,"name":"Miss Margarita Ratke","complete":0,"created_at":"2020-11-29T12:47:10.000000Z","updated_at":"2020-11-29T12:47:10.000000Z"},{"id":8,"name":"Jessyca Stokes","complete":0,"created_at":"2020-11-29T12:47:10.000000Z","updated_at":"2020-11-29T12:47:10.000000Z"},{"id":9,"name":"Dr. Mylene Doyle","complete":0,"created_at":"2020-11-29T12:47:10.000000Z","updated_at":"2020-11-29T12:47:10.000000Z"},{"id":10,"name":"Vilma Erdman","complete":0,"created_at":"2020-11-29T12:47:10.000000Z","updated_at":"2020-11-29T12:47:10.000000Z"},{"id":11,"name":"Dr. Keagan O'Conner PhD","complete":0,"created_at":"2020-11-29T12:47:10.000000Z","updated_at":"2020-11-29T12:47:10.000000Z"},{"id":12,"name":"Lou Heaney","complete":0,"created_at":"2020-11-29T12:47:10.000000Z","updated_at":"2020-11-29T12:47:10.000000Z"},{"id":13,"name":"Prof. Larissa Carroll","complete":0,"created_at":"2020-11-29T12:47:10.000000Z","updated_at":"2020-11-29T12:47:10.000000Z"},{"id":14,"name":"Mrs. Dejah Hane V","complete":0,"created_at":"2020-11-29T12:47:10.000000Z","updated_at":"2020-11-29T12:47:10.000000Z"},{"id":15,"name":"Doug Herman","complete":0,"created_at":"2020-11-29T12:47:10.000000Z","updated_at":"2020-11-29T12:47:10.000000Z"},{"id":16,"name":"Isabella Mills Jr.","complete":0,"created_at":"2020-11-29T12:47:10.000000Z","updated_at":"2020-11-29T12:47:10.000000Z"},{"id":17,"name":"Dr. Elwin Ullrich","complete":0,"created_at":"2020-11-29T12:47:10.000000Z","updated_at":"2020-11-29T12:47:10.000000Z"},{"id":18,"name":"Humberto Stehr","complete":0,"created_at":"2020-11-29T12:47:10.000000Z","updated_at":"2020-11-29T12:47:10.000000Z"},{"id":19,"name":"Frank Thiel","complete":0,"created_at":"2020-11-29T12:47:11.000000Z","updated_at":"2020-11-29T12:47:11.000000Z"},{"id":20,"name":"Maurice Runolfsson","complete":0,"created_at":"2020-11-29T12:47:11.000000Z","updated_at":"2020-11-29T12:47:11.000000Z"}]}

This was one way of doing it don't forget the official docs. Happy coding.

[convertkit=2908863]