Skip to main content
In 2D mode, the model that logs in is also the model that acts on every request. There is no separate principal or tenant — Auth::identity() and Auth::principal() both resolve to the same object. This is the right starting point for most apps: you can grow into 3D later without re-platforming your guard configuration.
2D is the correct choice for single-tenant APIs, M2M service accounts, and any app where users act only as themselves. If your users need to switch between tenant-scoped roles or memberships, see 3D multi-tenant setup instead.
1

Add the Identity and Principal contracts to your user model

Your user model must implement both Identity and Principal. The Authenticatable trait is a stateless drop-in for Laravel’s own Illuminate\Auth\Authenticatable — it suppresses the remember-token accessors that have no meaning in a sessionless package. The ActsAsPrincipal trait provides default implementations of the Principal contract methods (getPrincipalIdentifier(), getIdentity(), getTenant(), isActive()).
use Illuminate\Foundation\Auth\User;
use SineMacula\Laravel\Authentication\Contracts\Identity;
use SineMacula\Laravel\Authentication\Contracts\Principal;
use SineMacula\Laravel\Authentication\Traits\ActsAsPrincipal;
use SineMacula\Laravel\Authentication\Traits\Authenticatable;

class AppUser extends User implements Identity, Principal
{
    use Authenticatable, ActsAsPrincipal;
}
Point auth.providers.users.model at AppUser::class and the guard will use it for every bearer-token lookup.
2

Register the guard in config/auth.php

Register a guard with driver: jwt and wire it to your user provider. No extra service provider registration is needed — the package auto-registers its guard drivers.
// config/auth.php
'guards' => [
    'api' => [
        'driver'   => 'jwt',
        'provider' => 'users',
    ],
],

'providers' => [
    'users' => [
        'driver' => 'model',
        'model'  => App\Models\AppUser::class,
    ],
],
Protect routes with the standard Laravel middleware: auth:api.
3

Optionally add HasDevices for refresh-token rotation

Device tracking and refresh-token rotation are opt-in. If you need them, publish and run the devices migration, then implement HasDevices on your model with a morphMany relation:
php artisan vendor:publish --tag=authentication-migrations
php artisan migrate
use Illuminate\Database\Eloquent\Relations\MorphMany;
use SineMacula\Laravel\Authentication\Contracts\HasDevices;
use SineMacula\Laravel\Authentication\Models\Device;

class AppUser extends User implements Identity, Principal, HasDevices
{
    use Authenticatable, ActsAsPrincipal;

    public function devices(): MorphMany
    {
        return $this->morphMany(Device::class, 'authenticatable');
    }
}
Without HasDevices, the guard operates in access-only mode: Auth::device() returns null and $guard->refresh() is unavailable. You can add device tracking later — it is purely additive.
4

Issue an access token at login

After validating credentials, call issueAccessToken() on the guard-scoped JWT service. Pass the identity, the principal, and — if you are using device tracking — the device record. Pass null for the device in access-only mode.
use SineMacula\Laravel\Authentication\Facades\Auth;
use SineMacula\Laravel\Authentication\Jwt\RefreshTokenHasher;

// Resolve the identity however your login flow works
$identity   = AppUser::where('email', $request->email)->firstOrFail();
$principal  = $identity; // In 2D mode, identity and principal are the same model
$rotationId = RefreshTokenHasher::generate();

$device = $identity->devices()->create([
    'refresh_key' => RefreshTokenHasher::hash($rotationId),
    'os'          => $request->userAgent(),
]);

$accessToken  = Auth::jwt('api')->issueAccessToken($identity, $principal, $device);
$refreshToken = Auth::jwt('api')->issueRefreshToken($device, $rotationId, $principal);

return [
    'access_token'  => $accessToken,
    'refresh_token' => $refreshToken,
];
For access-only mode without device tracking, pass null as the third argument and skip issueRefreshToken:
$accessToken = Auth::jwt('api')->issueAccessToken($identity, $principal, null);
5

Verify the contextual accessors

Once a request arrives with a valid bearer token, the guard rehydrates both identity and principal from the database and binds them. In 2D mode both accessors return the same model instance.
use SineMacula\Laravel\Authentication\Facades\Auth;

Auth::check();       // true when a valid bearer token is present
Auth::user();        // AppUser — same as Auth::identity()
Auth::identity();    // AppUser — the authenticated subject
Auth::principal();   // AppUser — same instance as identity() in 2D mode
Auth::device();      // Device|null — the bound device, or null in access-only mode
Auth::tenant();      // null in 2D mode (no tenant model)
Auth::type();        // null in 2D mode
If you call Auth::identity() === Auth::principal() in 2D mode, the result is true — they are the same object.