Sanctum tutorial - Token Authentication in Laravel 11

Last update: 06-15-2024

In this blog post, we'll explore how to create a RESTful API in Laravel 11 for a mobile app frontend that will consume this API using token authentication with Sanctum.

Mobile App Authentication vs. SPA Authentication

As a rule of thumb, using an authentication token is generally acceptable for mobile applications. Authentication tokens can also be used in SPAs (single-page applications) but only if additional security measures are applied.

When developing a mobile app, you generally have more control on what your users can and cannot do as compared to your SPA users (such as React or Angular). This allows for a somewhat more straightforward process when it comes to authentication. Browsers give full control of the client side to the user, requiring meticulous attention to prevent easy access to sensitive data.

One significant difference is secure storage mechanisms available in mobile operating systems like Keychain on iOS and Keystore on Android. Sensitive information can be stored there and cannot be accessed or viewed by users. Browsers expose all data sent to the client side to the end user, and while mitigations exist, such as HTTP-only cookies that prevent JavaScript access, they do not eliminate all risks. The potential attack surface is larger in web applications due to how data is handled and stored on the client side.

Given the more limited nature of mobile applications, the authentication process is more straightforward, and essentialy involves receiving  a token from the server then using it in subsequent requests.

Token based authentication flow

[Step 1] 📱 → 🖥️
Mobile App sends authentication data to Server

[Step 2] 🖥️ ↔ 🖥️
Server validates authentication data

[Step 3] 🖥️ ← 📱
Server sends back an authentication token to Mobile App

[Step 4] 📱 → 🖥️
Mobile App sends a request with the token as a bearer

[Step 5] 🖥️ ↔ 🖥️
Server validates the token and processes the request

Steps 1-3 only happen once, then the same token can used for many requests (steps 4-5).

Sanctum - Mobile Authentication, the Laravel Way

Laravel Sanctum provides a simple and secure way to implement token-based authentication for mobile apps. While Sanctum also supports SPA authentication, this post focuses solely on token authentication. One of the benefits of Sanctum is its seamless integration with Laravel's broader authentication system, allowing easy access to user tokens via the User model.

Getting Started with Sanctum for Mobile App Token Authentication

First, create a new Laravel project:

composer create-project laravel/laravel token-auth-example

Then, install Sanctum in your Laravel project:

php artisan install:api

After installing Sanctum, the file "routes/api.php" will have been created.

api.php

You're probably familiar with routes/web.php, which is the default file for defining routes. api.php is created to handle requests from third-party apps and pages that do not originate directly from our server. There are two important differences between the routes in web.php and api.php:

  1. Route Prefix: Routes in api.php are automatically prefixed with /api/. For example, if we define the route /login in this file, it needs to be accessed as /api/login.

  2. CSRF Protection: In api.php, CSRF protection is not enforced for POST routes. When pages originate from the server, it's straightforward to inject the CSRF token into them, making it practical to expect CSRF tokens in the request's body. However, when working with a mobile app (where the pages do not originate from the server), injecting CSRF tokens is not feasible. This is not necessarily a problem, as CSRF attacks are primarily effective when cookies are involved and rely on the browser's automatic inclusion of cookies in requests. Bearer tokens, on the other hand, are not sent automatically and must be manually included in the request by the client, rendering CSRF attacks next to impossible.

Updating the User Model

To allow the User model access its tokens, we add the 'HasApiTokens' trait to it:

use Laravel\Sanctum\HasApiTokens;

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

Run the migrations

We now need to run the migrations to create the updated tables:

php artisan migrate

Example Code

Below is a minimal example demonstrating user registration, login, a protected route, and token revocation. These routes should be added to your routes/api.php file. You're invited to create a controller, of course, but to keep things simple, I gathered everything in one file:

<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Laravel\Sanctum\PersonalAccessToken;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Facades\Hash;
use App\Models\User;

Route::post('/register', function (Request $request) {
    $validator = Validator::make($request->all(), [
        'name' => 'required|string|max:255',
        'email' => 'required|string|email|max:255|unique:users',
        'password' => 'required|string|min:8',
    ]);

    if ($validator->fails()) {
        return response()->json(['errors' => $validator->errors()], 422);
    }

    User::create([
        'name' => $request->name,
        'email' => $request->email,
        'password' => Hash::make($request->password),
    ]);

    return response()->json(['message' => 'User registered successfully'], 201);
});

Route::post('/login', function (Request $request) {
    $request->validate([
        'email' => 'required|email',
        'password' => 'required',
    ]);

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

    if (!$user || !Hash::check($request->password, $user->password)) {
        return response()->json(['message' => 'Invalid credentials'], 401);
    }

    $token = $user->createToken('mobile_app_token')->plainTextToken;

    return response()->json(['token' => $token], 200);
});

Route::get('/protected-data', function (Request $request) {
    return response()->json(['message' => 'This text is only available to logged in users']);
})->middleware('auth:sanctum');

Route::get('revoke-token', function (Request $request) {
    $token = $request->bearerToken();
    if (!$token) {
        return response()->json(['message' => 'No token provided'], 400);
    }

    $tokenInstance = PersonalAccessToken::findToken($token);
    if ($tokenInstance) {
        $tokenInstance->delete();
        return response()->json(['message' => 'Token revoked'], 200);
    }

    return response()->json(['message' => 'Token not found'], 404);
});

Example HTTP Requests

To test this API using a tool like Postman, you can use the following HTTP requests:

Register a New User

POST /api/register

{
    "name": "John Doe",
    "email": "john@example.com",
    "password": "password"
}

Login a user

POST /api/login

{
    "email": "john@example.com",
    "password": "password"
}

The login request returns a user, we're going to use it in our following request, so copy and paste it.

Access Protected Data

GET /api/protected-data

Authorization: Bearer your_generated_token

Revoke Token

GET /api/revoke-token

Authorization: Bearer your_generated_token

0 Comments

Add a new comment: