P1 | Setup and API authentication using Laravel Sanctum and REST + GraphQL API
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.
- Laravel v11
- Laravel Sanctum v4
- Craftable PRO v2.0
- Lighthouse PHP v6
The preliminary structure of the upcoming blog posts is as follows:
- Part I. - Project setup and API authentication using Laravel Sanctum and REST + GraphQL API
- Part II. - REST vs. GraphQL API + more project actions
- Part III. - Error handling for REST API and GraphQL API
- Part IV. - quick intro to Craftable PRO and how easy you can generate a whole CRUD for your models + comparison of existing admin panel packages
- Part V. - Localization - compare different approaches - their pros & cons
- Part VI. - Access management / Roles & Permissions
- Part VII. - Automated testing for REST API and GraphQL API
- Part VIII. - API documentation using Swagger or Laravel API Doc generator
- Part IX. - File management: AWS S3, DigitalOcean Buckets - private vs. public files. Videos, attachments, whatever is too big to be kept on the web server directly.
- Part X. - Wrap-up + open questions + advanced topics
a) versioning - our opinion and experience
b) breaking changes - prompt update box vs. forced update
c) how to manage development and keep sync between BE and FE
d) offline support
e) push notifications
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:
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(
email: "test@test.com"
password: "incorrect"
deviceId: "iPhone 13 Pro"
)
}