Back to blog

P1 | Setup and API authentication using Laravel Sanctum and REST + GraphQL API

May 16, 2024
.
Kristína Odziomková

In today's world of mobile app development, managing content effectively is very important. Think of it like building a house—you need a solid foundation to support everything else. When it comes to handling content smoothly, our first choice is to use Laravel.

To share our insights on developing Laravel backends for mobile applications, we've opted to create a series of blog posts focusing on themes that we consider crucial and valuable. You won't require the Craftable PRO (our admin panel and CRUD generator) to derive value from these posts since our package will only be referenced in 1 or 2 parts of the series. The remaining content will be dedicated entirely to Laravel, its packages, and other open-source resources.

The preliminary structure of the upcoming blog posts is as follows:


In the first part of the series, we will have a look into the project setup and API authentication using Laravel Sanctum.


Setting up the Laravel project (15 minutes)

Following the official Laravel documentation, we will install Laravel with Sail and PostgreSQL. You can also use any other database, but PostgreSQL is our first choice for most project.

curl -s "https://laravel.build/mobile-content-management-system?with=pgsql" | bash

After this, you will be up and running.


Authentication (30 - 45 minutes)

To manage the authentication of mobile app users, we can use the default Laravel’s User model, or we can introduce a new user model and guard specifically tailored for this distinct API user type, as it differs from a common web user.

For authentication, we will utilize Laravel Sanctum and its API token authentication. This choice is driven by the fact that Laravel Sanctum is already integrated into our admin panel’s package dependencies.

Install Laravel Sanctum

Follow the current official documentation, however in Laravel 11 it is as easy as just calling:

sail artisan install:api

For older versions, install via Composer, publish the migrations and config, and run migrations:

sail composer require laravel/sanctum sail artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider" sail artisan migrate

Create the MobileAppUser model

Migration:

sail artisan make:migration create_mobile_app_users_table
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('mobile_app_users', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('email');
            $table->string('password');
            $table->timestamps();
        });
    }
    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('mobile_app_users');
    }
};

Model:

sail artisan make:model MobileAppUser
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Laravel\Sanctum\HasApiTokens;
class MobileAppUser extends Authenticatable
{
    use HasApiTokens;
    protected $guarded = [
        'id',
    ];
}

Migrate new table(s):

sail artisan migrate

Create a test user:

sail artisan tinker > App\Models\MobileAppUser::create(['name' => 'Test User', 'email' => 'test@test.com', 'password' => Hash::make('password')]);

Set up Laravel Sanctum API token authentication

Following the official documentation: Laravel Sanctum - API token authentication - add the Laravel\Sanctum\HasApiTokens trait on the MobileAppUser, if you haven’t yet in the previous step.

Set up new guard, user provider and middleware for an example route

In config/auth.php, add a new provider and guard:

'guards' => [
    ...,
    'mobile-app' => [
        'driver' => 'sanctum',
        'provider' => 'mobileAppUsers',
    ],
],
'providers' => [
    ...,
    'mobileAppUsers' => [
        'driver' => 'eloquent',
        'model' => App\Models\MobileAppUser::class,
    ],
],

In not yet present in your project, add a routes/api.php file and register it according to your Laravel version. For Laravel < 11, in the RouteServiceProvider, for Laravel >= 11 in the bootstrap/app.php file.

In the routes/api.php file add the first API route:

<?php
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;
Route::middleware('auth:mobile-app')->group(function () { // <-- put the same guard name as in the config/auth.php
    Route::get('/me', function () {
        $user = Auth::user();
        return [
            'name' => $user->name,
            'email' => $user->email,
        ];
    });
});

Generate an API token for your user to test the endpoint:

sail artisan tinker > App\Models\MobileAppUser::first()->createToken('test')->plainTextToken; "1|whVEITPSbU9bVyVbZMF3UKWnZWSsapysZDV3CjsJ588b9cd4"

Test the authentication via (for example) Postman:

The Sanctum API token is sent as a Bearer Token type of Authorization, and we also send an Accept: application/json header to get back a JSON response.

Now you only need to add a login, logout or register API routes to handle your authentication flow.


GraphQL/REST API (30 minutes - 2 hours)

The question whether and when we use REST vs. GraphQL API will be answered in the next part of this blog series.

In this section, we will implement a login and logout endpoints, both in REST and GraphQL for illustration.

REST API

routes/api.php

Route::middleware('auth:mobile-app')->group(function () {
    Route::get('me', 'App\Http\Controllers\AuthController@me')->name('me');
    Route::post('logout', 'App\Http\Controllers\AuthController@logout')->name('logout');
});

Route::post('login', 'App\Http\Controllers\AuthController@login')->name('login');

Create App\Http\Controllers\AuthController.php

sail artisan make:controller AuthController

and add the following code:

<?php
namespace App\Http\Controllers;

use App\Models\MobileAppUser;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;

class AuthController extends Controller
{
    public function me()
    {
        $user = Auth::user();

        return response()->json([
            'name' => $user->name,
            'email' => $user->email,
        ]);
    }
    
    public function login(Request $request)
    {
        $email = $request->input("email");
        $password = $request->input("password");
        $deviceId = $request->input("device_id");

        $user = MobileAppUser::query()->where("email", '=', $email)->first();

        if(!$user || !Hash::check($password, $user->password))
        {
            return response()->json('Incorrect credentials', 401);
        }

        $user = MobileAppUser::query()->where("email", '=', $email)->first();
        $token = $user->createToken($deviceId)->plainTextToken;

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

    public function logout(Request $request)
    {
        Auth::guard('mobile-app')->user()->currentAccessToken()->delete();

        return response()->json(true, 200);
    }
}

Postman examples

If you wish, test your new routes with Postman:

Login
Login
Logout
Logout
User detail
User detail

GraphQL API

For GraphQL in Laravel we recommend this Composer package: ​Lighthouse

At the time of writing, the version installed is v6 and the steps as follows:

sail composer require nuwave/lighthouse
sail artisan vendor:publish --tag=lighthouse-schema
sail artisan vendor:publish --tag=lighthouse-config
sail composer require mll-lab/laravel-graphiql

File graphql/schema.graphql:

Create queries for login, logout and detail of logged in user:

"A datetime string with format `Y-m-d H:i:s`, e.g. `2018-05-23 13:43:32`."
scalar DateTime @scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\DateTime")
"Indicates what fields are available at the top level of a query operation."
type Query {
    login(
        email: String!
        password: String!
        deviceId: String!
    ): String!
    logout: Boolean! @guard
    me: MobileAppUser! @guard
}
type MobileAppUser {
    name: String!
    email: String!
}

The @guard directive takes the guards from config/lighthouse.php, so set our authentication guard there:

'guards' => ['mobile-app'],

Create Query classes to handle the PHP code using this Artisan command, or manually:

sail artisan lighthouse:query Login
sail artisan lighthouse:query Logout
sail artisan lighthouse:query Me

Optionally generate IDE helper:

sail art lighthouse:ide-helper

Fill in the Query classes with the authentication logic:

app\GraphQL\Queries\Login.php:

<?php

namespace App\GraphQL\Queries;

use App\Models\MobileAppUser;
use Illuminate\Support\Facades\Hash;
use Nuwave\Lighthouse\Exceptions\AuthenticationException;

final readonly class Login
{
    /** @param array{} $args
     * @throws AuthenticationException
     */
    public function __invoke(null $_, array $args)
    {
        $email = $args['email'];
        $password = $args['password'];
        $deviceId = $args['deviceId'];

        $user = MobileAppUser::query()->where("email", '=', $email)->first();

        if(!$user || !Hash::check($password, $user->password))
        {
            throw new AuthenticationException();
        }

        $user = MobileAppUser::query()->where("email", '=', $email)->first();
        $token = $user->createToken($deviceId)->plainTextToken;

        return $token;
    }
}

app\GraphQL\Queries\Logout.php:

<?php

namespace App\GraphQL\Queries;

use Illuminate\Support\Facades\Auth;

final readonly class Logout
{
    /** @param  array{}  $args */
    public function __invoke(null $_, array $args)
    {
        Auth::guard('mobile-app')->user()->currentAccessToken()->delete();

        return true;
    }
}

app\GraphQL\Queries\Me.php:

<?php

namespace App\GraphQL\Queries;

use Illuminate\Support\Facades\Auth;

final readonly class Me
{
    /** @param  array{}  $args */
    public function __invoke(null $_, array $args)
    {
        $user = Auth::guard('mobile-app')->user();

        return [
            'name' => $user->name,
            'email' => $user->email,
        ];
    }
}

GraphiQL examples (at http://localhost/graphiql)

{
  login(
    email: "test@test.com"
    password: "password"
    deviceId: "iPhone 13 Pro"
  )
}
Login with correct credentials
Login with correct credentials
{
  login(
    email: "test@test.com"
    password: "incorrect"
    deviceId: "iPhone 13 Pro"
  )
}