---
title: "Auth"
description: "Autenticación basada en sesiones, el formulario de inicio de sesión preconstruido y el patrón del complemento de usuario desde la producción."
---

Nibiru incluye un núcleo de autenticación basado en sesiones (`Nibiru\Auth`) y un módulo `users` que te proporciona un formulario de inicio de sesión funcional, una verificación de autorización y el esquema de base de datos en tres comandos.

## El núcleo de autenticación

`Auth::auth($login, $password)` es la llamada de nivel más bajo. Realiza lo siguiente:

1. Busca al usuario por `user_login`.
2. Desencripta la contraseña almacenada utilizando el sal de `[SECURITY] password_hash`.
3. Si coincide, reemplaza `$_SESSION` con `['auth' => ['session_id' => …, 'user_id' => …, 'login' => …]]`.
```php
$auth = new \Nibiru\Auth();
if ($auth->auth($_POST['login'], $_POST['password'])) {
    View::forwardTo('/dashboard');
} else {
    View::assign(['error' => 'Invalid credentials.']);
}
```
:::caution
El esquema predeterminado almacena contraseñas con **AES_DECRYPT** claveadas por el sal de INI — reversibles. Adecuado para prototipado, **no** para producción pública. Reemplázalo con `password_hash()` / `password_verify()` para aplicaciones reales. Consulta *Reforzamiento*, a continuación.
:::

## El módulo de Usuarios

Genera una vez con la CLI:
```bash
./nibiru -m users
```
Luego ejecuta las migraciones correspondientes:
```bash
./nibiru -mi local
```
Ahora tienes:

- `application/module/users/` — la carpeta del módulo.
- `users` tabla — creada por `005-user.sql`.
- `User` plugin — `Nibiru\Module\Users\Plugin\User` con `isAuthorized()`, `loginForm()`, `currentUser()`.

## Un flujo de inicio de sesión completo

`application/controller/loginController.php`:
```php
<?php
namespace Nibiru;
use Nibiru\Adapter\Controller;
use Nibiru\Module\Users\Plugin\User;

class loginController extends Controller
{
    private User $user;

    public function __construct() {
        parent::__construct();
        $this->user = new User();
    }

    public function pageAction() {
        if ($this->user->isAuthorized()) {
            View::forwardTo('/');
            return;
        }
        View::assign([
            'title'     => 'Sign in',
            'loginForm' => $this->user->loginForm(),
        ]);
    }

    public function submitAction() {
        if ($_SERVER['REQUEST_METHOD'] !== 'POST') return;
        $auth = new Auth();
        if ($auth->auth($_POST['login'] ?? '', $_POST['password'] ?? '')) {
            View::forwardTo('/');
        } else {
            View::assign(['error' => 'Invalid login.']);
        }
    }

    public function logoutAction() {
        unset($_SESSION['auth']);
        session_regenerate_id(true);
        View::forwardTo('/login');
    }

    public function navigationAction() {
        JsonNavigation::getInstance()->loadJsonNavigationArray();
    }
}
```
`application/view/templates/login.tpl`:
```smarty
{include 'shared/header.tpl'}
<body>
{include file="navigation.tpl"}
<main class="container">
  <h1>{$title}</h1>
  {if $error}<div class="alert alert-danger">{$error}</div>{/if}
  {$loginForm nofilter}
</main>
{include 'shared/footer.tpl'}
</body>
```
`Form::action="/login/submit"` está configurado dentro de `loginForm()` para que el formulario se envíe a la acción correcta.

## Protegiendo controladores

La verificación sencilla:
```php
public function pageAction() {
    if (!$this->user->isAuthorized()) {
        View::forwardTo('/login');
        return;
    }
    // ...
}
```
Para las verificaciones basadas en roles, las aplicaciones de demostración utilizan el complemento `Acl` del mismo módulo:
```php
use Nibiru\Module\Users\Plugin\Acl;

if (!Acl::can('edit', 'documents')) {
    View::forwardTo('/forbidden');
    return;
}
```
Las tablas `acl`, `user_to_acl`, `acl-data` (migraciones 001, 008, 011) forman la base de roles y permisos.

## Aumento de la Seguridad

Para producción, reemplace `Auth::auth()` con una versión más robusta:
```php
namespace Nibiru;
use Nibiru\Pdo;

class HardenedAuth
{
    public function auth(string $login, string $password): bool {
        $row = Pdo::fetchRow(
            'SELECT user_id, user_pass FROM "user" WHERE user_login = :l AND user_account_active = 1',
            [':l' => $login]
        );
        if (!$row || !password_verify($password, $row['user_pass'])) {
            return false;
        }
        if (password_needs_rehash($row['user_pass'], PASSWORD_ARGON2ID)) {
            Pdo::update('user', [
                'user_pass' => password_hash($password, PASSWORD_ARGON2ID),
            ], ['user_id' => $row['user_id']]);
        }
        session_regenerate_id(true);
        $_SESSION['auth'] = [
            'session_id' => session_id(),
            'user_id'    => $row['user_id'],
            'login'      => $login,
        ];
        return true;
    }
}
```
Migrar filas existentes en el primer inicio de sesión con `password_needs_rehash`.

## CSRF

Nibiru no genera tokens CSRF automáticamente. Añádelos tú mismo:
```php
public function pageAction() {
    if (!isset($_SESSION['csrf'])) {
        $_SESSION['csrf'] = bin2hex(random_bytes(16));
    }
    View::assign(['csrf' => $_SESSION['csrf']]);
}

public function submitAction() {
    if (!hash_equals($_SESSION['csrf'] ?? '', $_POST['csrf'] ?? '')) {
        http_response_code(419);
        return;
    }
    // ...handle submission...
}
```
Incorpore `<input type="hidden" name="csrf" value="{$csrf}">` en su formulario.

## Inicio de sesión con código QR (patrón TPMS)

Las aplicaciones de producción incluyen un inicio de sesión basado en código QR con enlace mágico que emite tokens de corta duración. El flujo:

1. El usuario escanea el código QR → La URL es `/login/token/<one-time-token>`.
2. `tokenAction` valida y crea una sesión.

El marco ya depende de `bacon/bacon-qr-code` y `picqer/php-barcode-generator` a través de Composer, por lo que puedes representar códigos QR en línea:
```php
$writer = new \BaconQrCode\Writer(new \BaconQrCode\Renderer\ImageRenderer(
    new \BaconQrCode\Renderer\RendererStyle\RendererStyle(220),
    new \BaconQrCode\Renderer\Image\SvgImageBackEnd()
));
$svg = $writer->writeString('https://app.example.com/login/token/' . $token);
View::assign(['qr' => $svg]);
```
## Trucos comunes

- **`$_SESSION` es reemplazado, no fusionado** por el predeterminado `Auth::auth()`. Cualquier cosa que hayas almacenado antes del inicio de sesión se pierde. Guarda y restaura explícitamente si es necesario.
- **No hay límite de velocidad**. Agrega Fail2Ban o un observador estilo middleware.
- **Las cookies de sesión necesitan banderas.** Establece `session.cookie_secure = 1`, `session.cookie_httponly = 1` y `session.cookie_samesite = "Lax"` en php.ini para producción.
