---
title: "認証"
description: "セッションベースの認証、事前構築されたログインフォーム、およびプロダクションからのユーザープラグインパターン。"
---

Nibiru はセッションベースの認証コア (`Nibiru\Auth`) と `users` モジュールを搭載しており、3つのコマンドで動作するログインフォーム、権限チェック、データベーススキーマを提供します。

## 認証コア

`Auth::auth($login, $password)` は最下層の呼び出しです。このメソッドは：

1. `user_login` でユーザーを検索します。
2. `[SECURITY] password_hash` から取得したソルトを使用して、保存されたパスワードを復号化します。
3. マッチする場合、`$_SESSION` を `['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
デフォルトのスキーマは、INI塩でキー付けされた **AES_DECRYPT** でパスワードを保存します — 可逆です。プロトタイピングには適していますが、公開運用には **不適切** です。実際のアプリケーションでは `password_hash()` / `password_verify()` を使用してください。下記の *強化* を参照してください。
:::

## ユーザーモジュール

CLIで一度生成します：
```bash
./nibiru -m users
```
その後、対応するマイグレーションを実行します。
```bash
./nibiru -mi local
```
あなたには以下があります：

- `application/module/users/` — モジュールフォルダ。
- `users` テーブル — `005-user.sql` によって作成されました。
- `User` プラグイン — `Nibiru\Module\Users\Plugin\User` で `isAuthorized()`, `loginForm()`, `currentUser()` があります。

## 完全なログインフロー

`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"` は `loginForm()` 内で設定されているため、フォームは正しいアクションに投稿されます。

## コントローラーの保護

単純なチェック：
```php
public function pageAction() {
    if (!$this->user->isAuthorized()) {
        View::forwardTo('/login');
        return;
    }
    // ...
}
```
ロール認識のチェックのために、デモンストレーションアプリケーションは同じモジュールから `Acl` プラグインを使用しています。
```php
use Nibiru\Module\Users\Plugin\Acl;

if (!Acl::can('edit', 'documents')) {
    View::forwardTo('/forbidden');
    return;
}
```
テーブル `acl`, `user_to_acl`, `acl-data`（マイグレーション 001, 008, 011）が役割/権限の基盤を形成しています。

## 強化

本番環境では、`Auth::auth()` を強化されたバージョンに置き換えてください。
```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;
    }
}
```
既存の行を最初のログイン時に `password_needs_rehash` で移行します。

## CSRF

NibiruはCSRFトークンを生成しません。自分で追加してください：
```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...
}
```
フォームに `<input type="hidden" name="csrf" value="{$csrf}">` を埋め込んでください。

## QRコードログイン（TPMSパターン）

生産アプリケーションには、QRコードベースのマジックリンクログインが含まれています。このフローは以下の通りです：

1. ユーザーがQRコードをスキャン → URLは `/login/token/<one-time-token>` です。
2. `tokenAction` はトークンを検証し、セッションを作成します。

フレームワークはすでに `bacon/bacon-qr-code` と `picqer/php-barcode-generator` を Composer 経由で依存しているため、QR コードをインラインでレンダリングできます：
```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]);
```
## 一般的な落とし穴

- **`$_SESSION` は置き換えられ、マージされません**。デフォルトの `Auth::auth()` によって。ログイン前に保存したものは失われます。必要に応じて明示的に保存と復元してください。
- **レート制限はありません**。Fail2Ban またはミドルウェアスタイルの観察者を追加してください。
- **セッションクッキーにはフラグが必要です**。php.ini の `session.cookie_secure = 1`、`session.cookie_httponly = 1`、および `session.cookie_samesite = "Lax"` を本番環境で設定してください。
