認証モジュール

モジュールパス: /alier_sys/Auth.js

概要


認証処理をサポートするユーティリティモジュールです。開発者は認証処理の実装を気にすることなく認証機能をアプリケーションに組み込めます。

基本的な使い方

認証エージェントを作成してログインを呼び出すまでの流れは以下の通りです。

const agent = new AuthAgent({
    id: "any-id",
    protocols: new DigestProtocol({
        host: "example.com",
        path: "/login",
        method: "GET",
        getCredentialsCallback: (envelope) => {
            Alier.Model.auth.eventHandler.post(envelope);
        },
        challengeTimeoutDelay: 3_000,
        challengeInterval: 1_000,
    })
});

const { ok, code } = await agent.login({ retryUnauth: true });
  1. 1行目で認証エージェントを作成しています。引数は id と認証プロトコルです。
    1. id は認証エージェントの識別子であり、認証エージェント登録/取得インターフェースで使用されます。
    2. 認証プロトコルには Alier が標準で提供している Digest 認証プロトコルを渡しています。Digest 認証プロトコルの引数は以下の通りです:
      1. Digest 認証を実施する上で必要な情報(接続先のエンドポイントなど)を持つオブジェクト。
      2. ユーザのクレデンシャル[^credentials]を取得するためのコールバック関数。イベントハンドラを利用してプロミスのラッパーである envelope をイベントリスナに渡しています。
      3. タイムアウト時間(ミリ秒)。4. のインターバルより長く設定してください。
      4. チャレンジを試行する間隔(ミリ秒)。3. のタイムアウトより短く設定してください。
  2. 15 行目で認証エージェントのログインを呼び出し、成功または失敗の処理結果と失敗コードを取得しています。

終了コード

agent.login() の戻り値の終了コード code のうち、共通のコードは以下の通りです。

コード 意味
CANCELLED コールバックで渡された envelope で discard を呼ばれた
TIMEOUT タイムアウト
ABORT その他中断命令をキャッチ

他にもプロトコルごとに認証評価に失敗した場合などのコードがあります。

認証エージェント登録/取得インターフェース

レストフルオブジェクトで認証を使用するために Auth.js には認証エージェント登録、取得できるインターフェイス `AgentRepository が用意されています。

const { AgentRepository } = await Alier.import("/alier_sys/Auth.js");

// 登録
AgentRepository.registerAgent(new AuthAgent({ id: "any-id", protocols: new AnyProtocol() }));

// 取得
const agent = AgentRepository.pickAgent("any-id");

サンプルコード

ProtoViewLogic を交えたサンプルコード

ProtoViewLogic を交えたサンプルコードです。

// クレデンシャル入力を管理するProtoViewLogic
class InputCredentialsVL extends ProtoViewLogic {
    // 入力情報をログイン処理に渡すための関数
    #submit;
    // ログイン処理に入力がキャンセルされたことを通知するための関数
    #cancel;
    // クレデンシャルを渡したり、ログイン処理完了を受け取るためのプロミスラッパー
    #envelope;

    async messageHandler(msg) {
        return await msg.deliver({
            init: () => {
                // 入力情報をプロミスに結び付けるresolve/rejectを更新する
                Alier.Model.auth.update.addListener((envelope) => {
                    this.#envelope = envelope;
                });
                Alier.View.attach(this);
            },
            // 入力された情報を送信する
            submit: async (msg) => {
                // パラメータにユーザ名とパスワードが挿入されている
                const credentials = {
                    username: msg.param.username,
                    password: msg.param.password
                };
                // 処理の完了を受け取るプロミスラッパー
                const envelope = new Envelope();
                // 入力待ちをクレデンシャルとプロミスラッパーで解決する
                this.#envelope.post({ credentials, envelope });
                // envelope はオプショナルなので渡さなくてもよい
                // this.#envelope.post({ credentials });
                // プロミスラッパーを入れ替える
                this.#envelope = envelope;
            },
            // 入力待ちをキャンセルする
            cancel: async () => {
                const err = new Error("cancelled to input credentials");
                // ログイン処理を中断する
                this.#envelope.discard(err);
            },
        });
    }
}

const vl = new InputCredentialsVL();

// 認証処理を実行するエージェント
const agent = new AuthAgent({
    id: "any-id",
    protocols: new DigestProtocol({
        host: "example.com",
        path: "/login",
        method: "GET",
        getCredentialsCallback: (envelope) => {
            Alier.Model.auth.update.post(envelope);
        },
     }),
     challengeTimeoutDelay: 5_000,
     challengeInterval: 1_000,
});

// ログインを実行
// ok: ログイン成功または失敗を示す真偽値
// code: ログイン失敗時の失敗理由を表す失敗コードの文字列
const { ok, code } = await agent.login();

// ログイン成功
if (ok) {
    // ログイン成功時の処理
}

// 入力キャンセル
if (code === "CANCELLED") {
    // 入力キャンセル時の処理
}

// ...その他、入力情報が間違っているなどでログイン失敗

自動ログイン

起動時などでユーザ名とパスワードがすでに分かっている場合、ユーザ名とパスワードを自動取得するイベントリスナーをイベントハンドラーに追加することで自動ログインを実現できます。

class MyModel extends AlierModel {
    #agent;
    #eventHandler

    constructor() {
        super();
        return this.initialize(() => {
            this.#agent = new AuthAgent({
                id: "any-id",
                protocols: new DigestProtocl({
                    host: "http://example.com",
                    path: "/auth",
                    method: "GET",
                    getCredentialsCallback: (envelope) => {
                        this.#eventHandler.post(envelope);
                    },
                    challengeTimeoutDelay: 3_000,
                    challengeInterval: 1_000,
                }),
            });
        });
    }

    async login() {
        // ユーザ名とパスワードを自動取得するイベントリスナー
        const listener = (envelope) => {
            const { username, password } = ...; // ユーザ名とパスワードを何かしらの方法で取得
            envelope.post({ credentials: { username, password } });
        };
        this.#eventHandler.addListener(listener);

        // 認証に失敗したときはクレデンシャルの再入力がないので retryUnauth は false にする
        const { ok, code } = await this.#agent.login({ retryUnauth: false });

        // クレデンシャルの自動取得による自動ログインを無効化したい場合はリスナーを削除する
        this.#eventHandler.deleteListener(listener);
    }
}
warning

イベントハンドラーに ユーザ入力用のイベントリスナー がないか注意が必要です。

機能一覧


汎用機能

  • AuthAgent --- 認証処理をとりまとめるエージェント機能。
  • AgentRepository --- 認証エージェントの登録/取得インターフェース。

Digest 認証

インターフェースクラス

機能の関係性


--- config: class: hideEmptyMembersBox: true fontSize: 8px --- classDiagram AgentRepository "1" o-- "*" AuthAgent : エージェントを集約 AuthAgent "1" *-- "1..*" IAuthProtocol : プロトコルでエージェントを構成 IAuthProtocol "1" ..> "1" AuthEntity : エンティティ AuthEntity "1" ..> "1" IAuthKey : 認証情報