> ## 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 (autenticación con API tokens)

> Aprende a implementar la autenticación con API tokens y SPA con Laravel Sanctum. Desde la instalación del paquete hasta el uso avanzado, con un enfoque práctico.

## Qué es Sanctum

Laravel Sanctum es un paquete de autenticación ligero pensado para SPAs (single page applications), apps móviles y APIs sencillas. Puedes emitir y gestionar varios API tokens por usuario sin necesidad de dominar OAuth.

Sanctum cubre dos casos de uso:

| Modo de autenticación | Mecanismo                                | Uso típico                              |
| --------------------- | ---------------------------------------- | --------------------------------------- |
| **API tokens**        | Cabecera `Authorization: Bearer <token>` | Apps móviles, integraciones de terceros |
| **SPA**               | Cookie de sesión + protección CSRF       | Frontend propio (Vue/React, etc.)       |

<Info>
  Si tu SPA llama a la API que tú mismo controlas, usa el modo SPA. Si la API la consumen apps móviles o terceros, usa API tokens. Puedes usar uno solo o los dos.
</Info>

### Sanctum frente a Passport

|                   | Sanctum                    | Passport                          |
| ----------------- | -------------------------- | --------------------------------- |
| **Complejidad**   | Sencillo                   | OAuth2 completo                   |
| **Ideal para**    | SPA y apps móviles propias | Ser proveedor OAuth para terceros |
| **Tipo de token** | Personal access tokens     | Access tokens OAuth2              |

Si necesitas actuar como proveedor OAuth2 hacia otros, elige Passport. Para la mayoría de aplicaciones, Sanctum es suficiente.

***

## Instalación y configuración

### Instalación

Con el comando Artisan `install:api` se instala Sanctum.

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

Este comando:

* Instala el paquete `laravel/sanctum`.
* Publica la migración de la tabla `personal_access_tokens`.
* Ejecuta la migración.

### Añadir el trait `HasApiTokens`

Añade el trait `HasApiTokens` al modelo `User`.

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

use Laravel\Sanctum\HasApiTokens;

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

Con esto puedes usar métodos como `$user->createToken()` o `$user->tokens`.

***

## Autenticación con API tokens

### Flujo de tokens

```mermaid theme={null}
sequenceDiagram
    participant Client as Cliente
    participant API as API Laravel
    participant DB as Base de datos

    Client->>API: POST /login (email, password)
    API->>DB: Autenticar usuario
    DB-->>API: Datos del usuario
    API->>DB: Generar y guardar (hash) el token
    API-->>Client: Devuelve el plainTextToken

    Note over Client: El cliente guarda el token

    Client->>API: GET /api/user<br>Authorization: Bearer <token>
    API->>DB: Verificar token (SHA-256)
    DB-->>API: Usuario autenticado
    API-->>Client: Respuesta
```

### Emitir un token

Usa `createToken()`. La propiedad `plainTextToken` contiene el valor en texto plano. **Ese valor no se guarda en la base de datos**, por lo que debes devolvérselo al usuario inmediatamente.

```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');
```

En la base de datos se guarda el token con hash SHA-256.

### Configurar scopes (abilities)

Puedes asignar abilities (scopes) al token para restringir las operaciones que permite.

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

return $token->plainTextToken;
```

Al procesar la petición, comprueba los scopes.

```php theme={null}
if ($request->user()->tokenCan('server:update')) {
    // Ejecutar la operación de actualización
}

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

#### Comprobar scopes con middleware

Registra el alias del middleware en `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,  // Todas las abilities
        'ability'   => CheckForAnyAbility::class, // Alguna ability
    ]);
})
```

Aplica el middleware a las rutas.

```php theme={null}
// Solo tokens con check-status y place-orders a la vez
Route::get('/orders', function () {
    // ...
})->middleware(['auth:sanctum', 'abilities:check-status,place-orders']);

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

### Caducidad de los tokens

Por defecto los tokens de Sanctum no expiran. Configura la caducidad en minutos con `expiration` en `config/sanctum.php`.

```php theme={null}
// config/sanctum.php
'expiration' => 525600, // 365 días (en minutos)
```

También puedes indicar una caducidad por token.

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

Con caducidad activada, conviene programar la limpieza de tokens expirados.

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

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

### Revocar tokens

```php theme={null}
// Eliminar todos los tokens
$user->tokens()->delete();

// Eliminar el token de la petición actual
$request->user()->currentAccessToken()->delete();

// Eliminar un token concreto
$user->tokens()->where('id', $tokenId)->delete();
```

***

## Autenticación SPA

La autenticación SPA usa cookies de sesión, así que no necesitas emitir ni gestionar tokens. Es ideal para tu propio frontend (Vue, React, Next.js...) que consume tu API.

<Warning>
  Para usar la autenticación SPA, el SPA y la API deben compartir dominio de nivel superior (los subdominios pueden ser distintos). Además, las peticiones deben incluir `Accept: application/json` y las cabeceras `Referer` u `Origin`.
</Warning>

### Habilitar el middleware de Sanctum

Activa `statefulApi()` en `bootstrap/app.php`.

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

### Configurar los dominios first-party

Indica el dominio del SPA en la opción `stateful` de `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()
))),
```

### Configuración de CORS

Si tu API se consume desde otro subdominio, hay que configurar CORS.

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

En `config/cors.php` pon `supports_credentials` a `true`.

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

También en axios del frontend.

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

No olvides configurar el dominio de la cookie de sesión.

```php theme={null}
// config/session.php
'domain' => '.example.com', // Con el punto inicial
```

### Flujo de autenticación desde el SPA

```mermaid theme={null}
sequenceDiagram
    participant SPA as Frontend SPA
    participant API as API Laravel
    participant Session as Sesión

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

    SPA->>API: POST /login<br>Cabecera X-XSRF-TOKEN
    API->>Session: Crea la sesión
    API-->>SPA: Set-Cookie: laravel_session

    SPA->>API: GET /api/user<br>Cookie: laravel_session
    API->>Session: Comprueba la sesión
    Session-->>API: Usuario autenticado
    API-->>SPA: Datos del usuario
```

<Steps>
  <Step title="Obtener la cookie CSRF">
    Antes del login, llama a `/sanctum/csrf-cookie` para inicializar la protección CSRF.

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

  <Step title="Enviar la petición de login">
    POST al endpoint `/login`.

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

  <Step title="Enviar peticiones autenticadas">
    Las peticiones posteriores se autentican automáticamente con la cookie.

    ```js theme={null}
    const response = await axios.get('/api/user');
    console.log(response.data); // Datos del usuario autenticado
    ```
  </Step>
</Steps>

***

## Proteger rutas autenticadas

Aplica `auth:sanctum` a la ruta. Las peticiones no autenticadas reciben `401 Unauthorized`. El mismo middleware sirve para API tokens y SPA.

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

// Ruta única
Route::get('/user', function (Request $request) {
    return $request->user();
})->middleware('auth:sanctum');

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

***

## Ejemplo práctico: API de login que devuelve tokens

Ejemplo de autenticación con API tokens para una app móvil.

<Steps>
  <Step title="Crear el endpoint de login">
    ```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' => ['Las credenciales proporcionadas no son válidas.'],
            ]);
        }

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

  <Step title="Crear las rutas autenticadas">
    ```php theme={null}
    // routes/api.php

    Route::middleware('auth:sanctum')->group(function () {
        // Devuelve el usuario actual
        Route::get('/user', function (Request $request) {
            return $request->user();
        });

        // Cierra sesión revocando el token actual
        Route::post('/logout', function (Request $request) {
            $request->user()->currentAccessToken()->delete();

            return response()->json(['message' => 'Sesión cerrada.']);
        });

        // Cierra sesión en todos los dispositivos
        Route::post('/logout/all', function (Request $request) {
            $request->user()->tokens()->delete();

            return response()->json(['message' => 'Sesión cerrada en todos los dispositivos.']);
        });
    });
    ```
  </Step>

  <Step title="Consumirlo desde el cliente">
    ```js theme={null}
    // Login
    const { data } = await axios.post('/api/sanctum/token', {
        email: 'user@example.com',
        password: 'password',
        device_name: 'My iPhone',
    });

    const token = data.token;

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

***

## Pruebas

En los tests de Sanctum utiliza `Sanctum::actingAs()` para autenticar a un usuario y asignar abilities.

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

    test('se puede obtener el listado de tareas', function () {
        Sanctum::actingAs(
            User::factory()->create(),
            ['view-tasks']
        );

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

        $response->assertOk();
    });

    test('un token con todas las abilities puede acceder', 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>

***

## Resumen

<AccordionGroup>
  <Accordion title="Instalación">
    ```shell theme={null}
    # Instalar Sanctum y ejecutar migraciones
    php artisan install:api
    ```

    Añade `HasApiTokens` al modelo User:

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

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

  <Accordion title="API habitual">
    ```php theme={null}
    // Emitir token
    $token = $user->createToken('token-name')->plainTextToken;

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

    // Comprobar scope
    $user->tokenCan('read');   // true/false
    $user->tokenCant('write'); // true/false

    // Revocar
    $user->tokens()->delete();                        // Todos
    $request->user()->currentAccessToken()->delete(); // El actual
    $user->tokens()->where('id', $id)->delete();      // Uno concreto
    ```
  </Accordion>

  <Accordion title="Cuándo usar cada modo">
    * **API tokens**: apps móviles, terceros, CLI... clientes sin sesión.
    * **Autenticación SPA**: tu propio frontend Vue/React/Next.js sobre el mismo dominio (o subdominio). Más seguro y sin gestión de tokens.
  </Accordion>
</AccordionGroup>


## Related topics

- [Crear un servidor MCP con Laravel](/es/advanced/mcp-server.md)
- [Laravel Passport (implementación de servidor OAuth2)](/es/passport.md)
- [Laravel MCP](/es/mcp.md)
- [Enrutamiento](/es/routing.md)
- [Laravel Socialite (autenticación social)](/es/socialite.md)
