Skip to main content
This guide walks you through the shortest path to a working JWT bearer-token guard: install the package, configure a guard, implement a minimal Identity model, issue an access token from a login controller, and protect a route. By the end you’ll have a fully functional stateless auth layer wired into Laravel’s standard Auth facade and middleware.
This quickstart uses 2D mode, where the Identity and Principal are the same model — the user who logs in is the actor on whose behalf every request runs. This is the right starting point for most apps. If you’re building a multi-tenant product where a single human can act as different tenant-scoped roles, see Adoption Modes for the 3D setup.
1

Install the package

Add the package via Composer:
composer require sinemacula/laravel-authentication
Publish the config file and the device migration, then run the migration:
php artisan vendor:publish --tag=authentication-config
php artisan vendor:publish --tag=authentication-migrations
php artisan migrate
The migration creates the devices table used for refresh-token rotation and device tracking. If you only need access tokens and no refresh flow (common for M2M APIs), you can skip authentication-migrations entirely and omit the HasDevices contract from your model.
2

Set the JWT secret

Add your signing secret to .env. The package fails closed when a JWT guard is resolved with an empty or invalid secret — there is no silent fallback:
AUTHENTICATION_JWT_SECRET="a-strong-random-value-of-at-least-32-bytes"
Generate a suitable value with openssl rand -base64 48 or any equivalent tool. Keep this value out of version control.
3

Register the JWT guard

Open config/auth.php and register the jwt driver for your API guard, pointing it at your users provider:
'guards' => [
    'api' => [
        'driver'   => 'jwt',
        'provider' => 'users',
    ],
],

'providers' => [
    'users' => [
        'driver' => 'model',
        'model'  => App\Models\User::class,
    ],
],
The jwt driver is registered automatically by the package’s service provider. No additional bootstrapping is required.
4

Implement the Identity model

Your user model needs to implement the Identity and Principal contracts and pull in the corresponding traits. In 2D mode, the same class satisfies both contracts:
<?php

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use SineMacula\Laravel\Authentication\Contracts\Identity;
use SineMacula\Laravel\Authentication\Contracts\Principal;
use SineMacula\Laravel\Authentication\Traits\ActsAsPrincipal;
use SineMacula\Laravel\Authentication\Traits\Authenticatable as StatelessAuthenticatable;

class User extends Authenticatable implements Identity, Principal
{
    use StatelessAuthenticatable, ActsAsPrincipal;
}
A few things to note:
  • StatelessAuthenticatable re-exports Laravel’s own Authenticatable trait, but overrides the remember-token accessors with no-ops — this package is stateless and never writes a remember token.
  • ActsAsPrincipal provides the getPrincipalIdentifier() implementation required by the Principal contract, defaulting to the model’s primary key.
  • Point auth.providers.users.model at this class if it isn’t already App\Models\User.
5

Issue an access token

In your login controller, verify the user’s credentials and call Auth::jwt('api')->issueAccessToken() to mint a signed JWT. In 2D mode the identity and principal are the same model instance, and you pass null for the device when you’re not tracking devices:
<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use SineMacula\Laravel\Authentication\Facades\Auth;

class LoginController extends Controller
{
    public function __invoke(Request $request): JsonResponse
    {
        $request->validate([
            'email'    => ['required', 'email'],
            'password' => ['required', 'string'],
        ]);

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

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

        // In 2D mode: identity === principal. Pass null for device (access-only).
        $accessToken = Auth::jwt('api')->issueAccessToken($user, $user, null);

        return response()->json(['access_token' => $accessToken]);
    }
}
The returned string is a signed JWT. Your client should store it and include it as a Bearer token in the Authorization header on subsequent requests.
When you’re ready to add refresh-token rotation and device tracking, implement HasDevices on your model, resolve or create a Device record at login, and pass it as the third argument. See Refresh Rotation for the full flow.
6

Protect a route and read context

Use the standard auth:api middleware to protect any route. The JWT guard validates the bearer token, rehydrates the identity from your provider, and resolves the principal automatically:
// routes/api.php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\LoginController;

Route::post('/login', LoginController::class);

Route::middleware('auth:api')->group(function () {
    Route::get('/me', ProfileController::class);
});
Inside the protected controller, use Auth::identity() and Auth::principal() to read the contextual values set by the guard. In 2D mode both return the same User instance:
<?php

namespace App\Http\Controllers;

use Illuminate\Http\JsonResponse;
use SineMacula\Laravel\Authentication\Facades\Auth;

class ProfileController extends Controller
{
    public function __invoke(): JsonResponse
    {
        $identity  = Auth::identity();   // App\Models\User — who authenticated
        $principal = Auth::principal();  // App\Models\User — who is acting (same in 2D)

        return response()->json([
            'id'    => $identity->getKey(),
            'email' => $identity->email,
            'role'  => $principal->role ?? null,
        ]);
    }
}
Import SineMacula\Laravel\Authentication\Facades\Auth rather than Illuminate\Support\Facades\Auth to get IDE autocompletion for identity(), principal(), device(), tenant(), and type(). Both facades resolve through the same auth container key, so the package’s AuthManager is active either way.

Next steps

You now have a working JWT bearer-token guard. From here you can explore:

2D setup in depth

Add active-state enforcement, device tracking, and a full login/refresh/logout flow to your 2D app.

Refresh rotation

Issue refresh tokens, implement atomic rotation, and handle replay detection.

Identity, Principal, Device

Understand the three-context model and when each accessor returns what.