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

# Laravel Sanctum (API-tokenauthenticatie)

> Uitleg over het implementeren van API-tokenauthenticatie en SPA-authenticatie met Laravel Sanctum. Van de installatie van dit eenvoudige, lichtgewicht authenticatiepakket tot praktisch gebruik.

## Wat is Sanctum

Laravel Sanctum is een lichtgewicht authenticatiepakket voor SPA's (single-page applications), mobiele apps en eenvoudige API's. Zonder kennis van complexe OAuth kun je per gebruiker meerdere API-tokens uitgeven en beheren.

Sanctum lost twee problemen op.

| Authenticatiemodus         | Werking                                | Voornaamste toepassing                 |
| -------------------------- | -------------------------------------- | -------------------------------------- |
| **API-tokenauthenticatie** | `Authorization: Bearer <token>`-header | Mobiele apps en integraties van derden |
| **SPA-authenticatie**      | Sessiecookie + CSRF-bescherming        | Eigen frontend (Vue/React e.d.)        |

<Info>
  Roep je de API aan vanuit je eigen SPA, gebruik dan SPA-authenticatie. Gebruiken mobiele apps of derden je API, gebruik dan API-tokenauthenticatie. Je mag ook gerust maar één van beide gebruiken.
</Info>

### Sanctum of Passport?

|                   | Sanctum                     | Passport                         |
| ----------------- | --------------------------- | -------------------------------- |
| **Complexiteit**  | Eenvoudig                   | Volwaardige OAuth2               |
| **Geschikt voor** | Eigen SPA's en mobiele apps | OAuth-provider voor externe apps |
| **Tokentype**     | Personal access tokens      | OAuth2-accesstokens              |

Moet je als OAuth2-provider fungeren voor externe diensten, kies dan Passport; voor de meeste applicaties volstaat Sanctum.

***

## Installatie en configuratie

### Installatie

Sanctum wordt opgezet door simpelweg het Artisan-commando `install:api` uit te voeren.

```shell theme={null}
php artisan install:api
```

Dit commando doet automatisch het volgende:

* Installeren van het pakket `laravel/sanctum`
* Publiceren van het migratiebestand voor de tabel `personal_access_tokens`
* Uitvoeren van de migraties

### De HasApiTokens-trait toevoegen

Voeg de trait `HasApiTokens` toe aan je `User`-model.

```php theme={null}
// app/Models/User.php

use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;
}
```

Hierna kun je methoden zoals `$user->createToken()` en `$user->tokens` gebruiken.

***

## API-tokenauthenticatie

### Tokenflow

```mermaid theme={null}
sequenceDiagram
    participant Client as Client
    participant API as Laravel API
    participant DB as Database

    Client->>API: POST /login (email, password)
    API->>DB: Gebruiker authenticeren
    DB-->>API: Gebruikersgegevens
    API->>DB: Token genereren en opslaan (hash)
    API-->>Client: plainTextToken teruggeven

    Note over Client: Token opslaan

    Client->>API: GET /api/user<br>Authorization: Bearer <token>
    API->>DB: Token verifiëren (SHA-256)
    DB-->>API: Geauthenticeerde gebruiker
    API-->>Client: Response
```

### Een token uitgeven

Met de methode `createToken()` geef je een token uit. Via de property `plainTextToken` haal je de tokenwaarde in platte tekst op. **Het platte-teksttoken wordt niet in de database opgeslagen**, dus je moet het direct na uitgifte aan de gebruiker teruggeven.

```php theme={null}
use Illuminate\Http\Request;

Route::post('/tokens/create', function (Request $request) {
    $token = $request->user()->createToken($request->token_name);

    return ['token' => $token->plainTextToken];
})->middleware('auth');
```

In de database wordt het token opgeslagen als een SHA-256-hash.

### Scopes (abilities) instellen

Door abilities (scopes) aan een token toe te kennen, beperk je welke acties met dat token mogelijk zijn.

```php theme={null}
// Token met scopes uitgeven
$token = $user->createToken('mobile-app', ['server:update', 'server:read']);

return $token->plainTextToken;
```

Tijdens het verwerken van een request controleer je de scopes van het token.

```php theme={null}
if ($request->user()->tokenCan('server:update')) {
    // Update-actie uitvoeren
}

if ($request->user()->tokenCant('server:update')) {
    abort(403);
}
```

#### Scopes controleren met middleware

Registreer de middleware-aliassen in `bootstrap/app.php`.

```php theme={null}
use Laravel\Sanctum\Http\Middleware\CheckAbilities;
use Laravel\Sanctum\Http\Middleware\CheckForAnyAbility;

->withMiddleware(function (Middleware $middleware): void {
    $middleware->alias([
        'abilities' => CheckAbilities::class,  // Heeft alle abilities
        'ability'   => CheckForAnyAbility::class, // Heeft minstens één van de abilities
    ]);
})
```

Pas de middleware toe op je routes.

```php theme={null}
// Alleen tokens toestaan die zowel check-status als place-orders hebben
Route::get('/orders', function () {
    // ...
})->middleware(['auth:sanctum', 'abilities:check-status,place-orders']);

// Tokens toestaan die check-status of place-orders hebben
Route::get('/orders', function () {
    // ...
})->middleware(['auth:sanctum', 'ability:check-status,place-orders']);
```

### Geldigheidsduur van tokens

Standaard verlopen Sanctum-tokens niet. Met de optie `expiration` in `config/sanctum.php` stel je een geldigheidsduur in minuten in.

```php theme={null}
// config/sanctum.php
'expiration' => 525600, // 365 dagen (in minuten)
```

Je kunt ook per token een vervaldatum opgeven.

```php theme={null}
$token = $user->createToken(
    'token-name',
    ['*'],
    now()->addWeeks(1) // Verloopt over 1 week
)->plainTextToken;
```

Heb je een geldigheidsduur ingesteld, plan dan het periodiek verwijderen van verlopen tokens in.

```php theme={null}
use Illuminate\Support\Facades\Schedule;

Schedule::command('sanctum:prune-expired --hours=24')->daily();
```

### Tokens intrekken

```php theme={null}
// Alle tokens verwijderen
$user->tokens()->delete();

// Het token van het huidige request verwijderen
$request->user()->currentAccessToken()->delete();

// Een specifiek token verwijderen
$user->tokens()->where('id', $tokenId)->delete();
```

***

## SPA-authenticatie

SPA-authenticatie gebruikt een sessiecookie, dus je hoeft geen tokens uit te geven of te beheren. Het is geschikt wanneer je de API aanroept vanuit je eigen frontend (Vue, React, Next.js e.d.).

<Warning>
  Om SPA-authenticatie te gebruiken moeten de SPA en de API hetzelfde top-level domein delen (subdomeinen mogen verschillen). Verder moeten requests de header `Accept: application/json` en een `Referer`- of `Origin`-header bevatten.
</Warning>

### De Sanctum-middleware inschakelen

Schakel de middleware `statefulApi()` in via `bootstrap/app.php`.

```php theme={null}
->withMiddleware(function (Middleware $middleware): void {
    $middleware->statefulApi();
})
```

### First-party domeinen instellen

Stel het domein van je SPA in via de optie `stateful` in `config/sanctum.php`.

```php theme={null}
// config/sanctum.php
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
    '%s%s',
    'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
    Sanctum::currentApplicationUrlWithPort()
))),
```

### CORS configureren

Roep je de API aan vanaf een ander subdomein, dan is CORS-configuratie nodig.

```shell theme={null}
php artisan config:publish cors
```

Stel in `config/cors.php` de optie `supports_credentials` in op `true`.

```php theme={null}
// config/cors.php
'supports_credentials' => true,
```

Ook axios in de frontend moet worden geconfigureerd.

```js theme={null}
// resources/js/bootstrap.js
axios.defaults.withCredentials = true;
axios.defaults.withXSRFToken = true;
```

Vergeet ook de domeininstelling van de sessiecookie niet.

```php theme={null}
// config/session.php
'domain' => '.example.com', // Begin met een punt
```

### Authenticatieflow vanuit de SPA

```mermaid theme={null}
sequenceDiagram
    participant SPA as SPA-frontend
    participant API as Laravel API
    participant Session as Sessie

    SPA->>API: GET /sanctum/csrf-cookie
    API-->>SPA: XSRF-TOKEN-cookie instellen

    SPA->>API: POST /login<br>X-XSRF-TOKEN-header
    API->>Session: Sessie aanmaken
    API-->>SPA: Set-Cookie: laravel_session

    SPA->>API: GET /api/user<br>Cookie: laravel_session
    API->>Session: Sessie controleren
    Session-->>API: Geauthenticeerde gebruiker
    API-->>SPA: Gebruikersgegevens
```

<Steps>
  <Step title="Het CSRF-cookie ophalen">
    Roep vóór het inloggen het endpoint `/sanctum/csrf-cookie` aan om de CSRF-bescherming te initialiseren.

    ```js theme={null}
    await axios.get('/sanctum/csrf-cookie');
    ```
  </Step>

  <Step title="Het loginrequest versturen">
    Stuur een POST-request naar het `/login`-endpoint.

    ```js theme={null}
    await axios.post('/login', {
        email: 'user@example.com',
        password: 'password',
    });
    ```
  </Step>

  <Step title="Geauthenticeerde requests versturen">
    Requests na het inloggen worden automatisch geauthenticeerd via het sessiecookie.

    ```js theme={null}
    const response = await axios.get('/api/user');
    console.log(response.data); // Gegevens van de ingelogde gebruiker
    ```
  </Step>
</Steps>

***

## Geauthenticeerde routes beschermen

Pas de middleware `auth:sanctum` toe op een route en niet-geauthenticeerde requests krijgen een `401 Unauthorized` terug. Deze ene middleware handelt zowel API-token- als SPA-authenticatie af.

```php theme={null}
use Illuminate\Http\Request;

// Eén route beschermen
Route::get('/user', function (Request $request) {
    return $request->user();
})->middleware('auth:sanctum');

// Een routegroep beschermen
Route::middleware('auth:sanctum')->group(function () {
    Route::get('/profile', [ProfileController::class, 'show']);
    Route::put('/profile', [ProfileController::class, 'update']);
    Route::get('/posts', [PostController::class, 'index']);
});
```

***

## Praktijkvoorbeeld: login-API met tokenuitgifte

Een voorbeeld van API-tokenauthenticatie voor een mobiele app.

<Steps>
  <Step title="Het login-endpoint maken">
    ```php theme={null}
    // routes/api.php

    use App\Models\User;
    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Hash;
    use Illuminate\Validation\ValidationException;

    Route::post('/sanctum/token', function (Request $request) {
        $request->validate([
            'email'       => ['required', 'email'],
            'password'    => ['required'],
            'device_name' => ['required', 'string'],
        ]);

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

        if (! $user || ! Hash::check($request->password, $user->password)) {
            throw ValidationException::withMessages([
                'email' => ['De opgegeven inloggegevens zijn onjuist.'],
            ]);
        }

        return response()->json([
            'token' => $user->createToken($request->device_name)->plainTextToken,
        ]);
    });
    ```
  </Step>

  <Step title="Geauthenticeerde routes maken">
    ```php theme={null}
    // routes/api.php

    Route::middleware('auth:sanctum')->group(function () {
        // Gegevens van de huidige gebruiker teruggeven
        Route::get('/user', function (Request $request) {
            return $request->user();
        });

        // Het huidige token intrekken en uitloggen
        Route::post('/logout', function (Request $request) {
            $request->user()->currentAccessToken()->delete();

            return response()->json(['message' => 'Je bent uitgelogd.']);
        });

        // Uitloggen op alle apparaten
        Route::post('/logout/all', function (Request $request) {
            $request->user()->tokens()->delete();

            return response()->json(['message' => 'Je bent op alle apparaten uitgelogd.']);
        });
    });
    ```
  </Step>

  <Step title="Requests versturen vanuit de client">
    ```js theme={null}
    // Inloggen
    const { data } = await axios.post('/api/sanctum/token', {
        email: 'user@example.com',
        password: 'password',
        device_name: 'My iPhone',
    });

    const token = data.token;

    // Geauthenticeerd request
    const response = await axios.get('/api/user', {
        headers: {
            Authorization: `Bearer ${token}`,
        },
    });
    ```
  </Step>
</Steps>

***

## Testen

Bij het testen van Sanctum gebruik je `Sanctum::actingAs()` om een gebruiker te authenticeren en de toe te kennen abilities op te geven.

<Tabs>
  <Tab title="Pest">
    ```php theme={null}
    use App\Models\User;
    use Laravel\Sanctum\Sanctum;

    test('takenlijst kan worden opgehaald', function () {
        Sanctum::actingAs(
            User::factory()->create(),
            ['view-tasks']
        );

        $response = $this->get('/api/tasks');

        $response->assertOk();
    });

    test('toegang met een token met alle abilities', function () {
        Sanctum::actingAs(
            User::factory()->create(),
            ['*']
        );

        $response = $this->get('/api/tasks');

        $response->assertOk();
    });
    ```
  </Tab>

  <Tab title="PHPUnit">
    ```php theme={null}
    use App\Models\User;
    use Laravel\Sanctum\Sanctum;

    public function test_task_list_can_be_retrieved(): void
    {
        Sanctum::actingAs(
            User::factory()->create(),
            ['view-tasks']
        );

        $response = $this->get('/api/tasks');

        $response->assertOk();
    }
    ```
  </Tab>
</Tabs>

***

## Samenvatting

<AccordionGroup>
  <Accordion title="Installatiestappen op een rij">
    ```shell theme={null}
    # Sanctum installeren en migraties uitvoeren
    php artisan install:api
    ```

    Voeg de trait `HasApiTokens` toe aan het User-model:

    ```php theme={null}
    use Laravel\Sanctum\HasApiTokens;

    class User extends Authenticatable
    {
        use HasApiTokens, HasFactory, Notifiable;
    }
    ```
  </Accordion>

  <Accordion title="Veelgebruikte API's op een rij">
    ```php theme={null}
    // Token uitgeven
    $token = $user->createToken('token-name')->plainTextToken;

    // Token met scopes uitgeven
    $token = $user->createToken('token-name', ['read', 'write'])->plainTextToken;

    // Scopes controleren
    $user->tokenCan('read');   // true/false
    $user->tokenCant('write'); // true/false

    // Tokens intrekken
    $user->tokens()->delete();                        // Alle tokens
    $request->user()->currentAccessToken()->delete(); // Huidige token
    $user->tokens()->where('id', $id)->delete();      // Specifiek token
    ```
  </Accordion>

  <Accordion title="Kiezen tussen API-tokenauthenticatie en SPA-authenticatie">
    * **API-tokenauthenticatie**: voor clients zonder sessie, zoals mobiele apps, derden en CLI-tools.
    * **SPA-authenticatie**: voor een SPA op hetzelfde domein (of subdomein), zoals je eigen Vue/React/Next.js-frontend. Veiliger en zonder tokenbeheer.
  </Accordion>
</AccordionGroup>


## Related topics

- [Laravel Passport (OAuth2-serverimplementatie)](/nl/passport.md)
- [Laravel MCP](/nl/mcp.md)
- [Een custom authenticatieguard implementeren](/nl/advanced/custom-auth-guard.md)
- [CSRF-bescherming](/nl/csrf.md)
- [Routing](/nl/routing.md)
