> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sinemacula.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Set Up 2D Authentication (Identity = Principal)

> Configure Laravel Authentication in 2D mode where a single model acts as both Identity and Principal — ideal for simple or single-tenant apps.

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.

<Tip>
  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](/guides/3d-setup) instead.
</Tip>

<Steps>
  <Step title="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()`).

    ```php theme={null}
    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.
  </Step>

  <Step title="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.

    ```php theme={null}
    // 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`.
  </Step>

  <Step title="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:

    ```bash theme={null}
    php artisan vendor:publish --tag=authentication-migrations
    php artisan migrate
    ```

    ```php theme={null}
    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.
  </Step>

  <Step title="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.

    ```php theme={null}
    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`:

    ```php theme={null}
    $accessToken = Auth::jwt('api')->issueAccessToken($identity, $principal, null);
    ```
  </Step>

  <Step title="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.

    ```php theme={null}
    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.
  </Step>
</Steps>
