---
title: "Auth"
description: "Sitzungs-basierte Authentifizierung, das vordefinierte Anmeldeformular und das Benutzer-Plugin-Muster aus der Produktion."
---

Nibiru bietet einen auf Sitzungen basierenden Authentifizierungskern (`Nibiru\Auth`) und ein Modul `users`, das Ihnen eine funktionierende Anmeldeformular, eine Autorisierung überprüft und das Datenbankschema in drei Befehlen bereitstellt.

## Der Auth-Kernpunkte

`Auth::auth($login, $password)` ist der niedrigste Funktionsaufruf. Er:

1. Sucht den Benutzer nach `user_login`.
2. Entschlüsselt das gespeicherte Passwort mit dem Salt aus `[SECURITY] password_hash`.
3. Bei Übereinstimmung wird `$_SESSION` durch `['auth' => ['session_id' => …, 'user_id' => …, 'login' => …]]` ersetzt.
```php
$auth = new \Nibiru\Auth();
if ($auth->auth($_POST['login'], $_POST['password'])) {
    View::forwardTo('/dashboard');
} else {
    View::assign(['error' => 'Invalid credentials.']);
}
```
:::caution
Das Standard-Schema speichert Passwörter mit **AES_DECRYPT**, die durch den INI-Salt verschlüsselt sind — umkehrenbar. Ausreichend für das Prototyping, **nicht** für öffentliche Produktion. Ersetzen Sie es durch `password_hash()` / `password_verify()` für echte Anwendungen. Siehe unten unter *Hardening*.
:::

## Das Modul Benutzer

Generieren Sie es einmal mit der Befehlszeilenschnittstelle (CLI):
```bash
./nibiru -m users
```
Führen Sie dann die entsprechenden Migrationen aus:
```bash
./nibiru -mi local
```
Sie haben jetzt:

- `application/module/users/` — der Modulordner.
- `users` Tabelle — erstellt durch `005-user.sql`.
- `User` Plugin — `Nibiru\Module\Users\Plugin\User` mit `isAuthorized()`, `loginForm()`, `currentUser()`.

## Ein vollständiger Anmeldeprozess

`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"` wird in `loginForm()` gesetzt, sodass das Formular an die richtige Aktion gepostet wird.

## Schutz von Controllern

Die einfache Überprüfung:
```php
public function pageAction() {
    if (!$this->user->isAuthorized()) {
        View::forwardTo('/login');
        return;
    }
    // ...
}
```
Für rollebasierte Überprüfungen verwenden die Showcase-Anwendungen den `Acl`-Plugin aus dem gleichen Modul:
```php
use Nibiru\Module\Users\Plugin\Acl;

if (!Acl::can('edit', 'documents')) {
    View::forwardTo('/forbidden');
    return;
}
```
Tabellen `acl`, `user_to_acl`, `acl-data` (Migrationen 001, 008, 011) bilden die Grundlage für Rollen und Berechtigungen.

## Härterung

Für die Produktion ersetzen Sie `Auth::auth()` durch eine verstärkte Version:
```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;
    }
}
```
Migrieren Sie vorhandene Zeilen beim ersten Anmelden mit `password_needs_rehash`.

## CSRF

Nibiru generiert keine CSRF-Token für Sie. Fügen Sie diese selbst hinzu:
```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...
}
```
Fügen Sie `<input type="hidden" name="csrf" value="{$csrf}">` in Ihr Formular ein.

## QR-Code-Anmeldung (TPMS-Muster)

Produktionsanwendungen umfassen eine QR-codes-basierte Magics-Link-Anmeldung, die kurzlebige Token ausgibt. Der Ablauf:

1. Der Benutzer scannen einen QR-Code → Die URL lautet `/login/token/<one-time-token>`.
2. `tokenAction` validiert und erstellt eine Sitzung.

Das Framework hängt bereits von `bacon/bacon-qr-code` und `picqer/php-barcode-generator` über Composer ab, sodass Sie QR-Codes direkt rendern können:
```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]);
```
## Häufige Fallen

- **`$_SESSION` wird nicht zusammengeführt, sondern ersetzt** durch die Standardmethode `Auth::auth()`. Alles, was Sie vor dem Login gespeichert hatten, geht verloren. Speichern und wiederherstellen Sie explizit, wenn dies erforderlich ist.
- **Keine Rate Limiting**. Fügen Sie Fail2Ban oder einen middleware-artigen Beobachter hinzu.
- **Sitzungs-Cookies benötigen Flags.** Setzen Sie `session.cookie_secure = 1`, `session.cookie_httponly = 1` und `session.cookie_samesite = "Lax"` in der php.ini für die Produktion.
