Keycloak Node.js アダプター

サーバーサイド JavaScript アプリを保護する Node.js アダプター

Keycloak は、サーバーサイド JavaScript アプリを保護するために Connect の上に構築された Node.js アダプターを提供します。その目標は、Express.js のようなフレームワークと統合できる柔軟性を持たせることでした。このアダプターは、内部で OpenID Connect プロトコルを使用しています。OpenID Connect のエンドポイントと機能に関するより一般的な情報については、OpenID Connect によるアプリケーションとサービスの保護 ガイドをご覧ください。

このライブラリは、Keycloak organization から直接ダウンロードでき、ソースは GitHub で入手できます。

Node.js アダプターを使用するには、まず Keycloak 管理コンソールでアプリケーションのクライアントを作成する必要があります。このアダプターは、public、confidential、bearer-only のアクセスタイプをサポートしています。どれを選択するかは、ユースケースのシナリオによって異なります。

クライアントが作成されたら、右上にある Action をクリックし、Download adapter config を選択します。Format には *Keycloak OIDC JSON を選択し、Download をクリックします。ダウンロードされた keycloak.json ファイルは、プロジェクトのルートフォルダーに配置します。

インストール

Node.js がすでにインストールされていることを前提として、アプリケーション用のフォルダーを作成します。

mkdir myapp && cd myapp

npm init コマンドを使用して、アプリケーション用の package.json を作成します。次に、依存関係リストに Keycloak connect アダプターを追加します。

    "dependencies": {
        "keycloak-connect": "26.1.1"
    }

使用方法

Keycloak クラスをインスタンス化します。

Keycloak クラスは、アプリケーションとの構成と統合の中心点を提供します。最も簡単な作成方法は、引数なしで実行することです。

プロジェクトのルートディレクトリに server.js というファイルを作成し、次のコードを追加します。

    const session = require('express-session');
    const Keycloak = require('keycloak-connect');

    const memoryStore = new session.MemoryStore();
    const keycloak = new Keycloak({ store: memoryStore });

express-session 依存関係をインストールします。

    npm install express-session

server.js スクリプトを開始するには、package.json の 'scripts' セクションに次のコマンドを追加します。

    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "start": "node server.js"
    },

これで、次のコマンドでサーバーを実行できるようになりました。

    npm run start

デフォルトでは、これはアプリケーションのメイン実行可能ファイル(この場合はルートフォルダー)の横にある keycloak.json という名前のファイルを検索し、公開鍵、レルム名、さまざまな URL などの Keycloak 固有の設定を初期化します。

その場合、Keycloak 管理コンソールにアクセスするには Keycloak デプロイメントが必要です。

Podman または Docker で Keycloak 管理コンソールをデプロイする方法については、リンク先をご覧ください。

これで、Keycloak 管理コンソール → clients (左側のサイドバー) → クライアントを選択 → Installation → Format Option → Keycloak OIDC JSON → Download にアクセスして keycloak.json ファイルを取得する準備ができました。

ダウンロードしたファイルをプロジェクトのルートフォルダーに貼り付けます。

このメソッドを使用したインスタンス化では、すべての妥当なデフォルトが使用されます。代替案として、keycloak.json ファイルではなく、構成オブジェクトを提供することも可能です。

    const kcConfig = {
        clientId: 'myclient',
        bearerOnly: true,
        serverUrl: 'https://#:8080{kc_base_path}',
        realm: 'myrealm',
        realmPublicKey: 'MIIBIjANB...'
    };

    const keycloak = new Keycloak({ store: memoryStore }, kcConfig);

アプリケーションは、次を使用することで、ユーザーを優先するアイデンティティプロバイダーにリダイレクトすることもできます。

    const keycloak = new Keycloak({ store: memoryStore, idpHint: myIdP }, kcConfig);
Web セッションストアの構成

Web セッションを使用して認証のサーバーサイド状態を管理する場合は、少なくとも store パラメーターを指定して Keycloak(…​) を初期化し、express-session が使用している実際のセッションストアを渡す必要があります。

    const session = require('express-session');
    const memoryStore = new session.MemoryStore();

    // Configure session
    app.use(
      session({
        secret: 'mySecret',
        resave: false,
        saveUninitialized: true,
        store: memoryStore,
      })
    );

    const keycloak = new Keycloak({ store: memoryStore });
カスタムスコープ値の渡し

デフォルトでは、スコープ値 openid がクエリパラメーターとして Keycloak のログイン URL に渡されますが、追加のカスタム値を追加できます。

    const keycloak = new Keycloak({ scope: 'offline_access' });

ミドルウェアのインストール

インスタンス化したら、ミドルウェアを connect 対応アプリにインストールします。

そのためには、まず Express をインストールする必要があります。

    npm install express

次に、以下に示すように、プロジェクトで Express を require します。

    const express = require('express');
    const app = express();

そして、以下のコードを追加して、Express で Keycloak ミドルウェアを構成します。

    app.use( keycloak.middleware() );

最後に、main.js に次のコードを追加して、ポート 3000 で HTTP リクエストをリッスンするようにサーバーを設定しましょう。

    app.listen(3000, function () {
        console.log('App listening on port 3000');
    });

プロキシの構成

アプリケーションが SSL 接続を終端するプロキシの背後で実行されている場合、Express は express behind proxies ガイドに従って構成する必要があります。正しくないプロキシ構成を使用すると、無効なリダイレクト URI が生成される可能性があります。

構成例

    const app = express();

    app.set( 'trust proxy', true );

    app.use( keycloak.middleware() );

リソースの保護

シンプルな認証

リソースにアクセスする前にユーザーが認証されていることを強制するには、引数なしバージョンの keycloak.protect() を使用するだけです。

    app.get( '/complain', keycloak.protect(), complaintHandler );
ロールベースの認可

現在のアプリのアプリケーションロールでリソースを保護するには

    app.get( '/special', keycloak.protect('special'), specialHandler );

別の アプリケーションロールでリソースを保護するには

    app.get( '/extra-special', keycloak.protect('other-app:special'), extraSpecialHandler );

レルムロールでリソースを保護するには

    app.get( '/admin', keycloak.protect( 'realm:admin' ), adminHandler );
リソースベースの認可

リソースベースの認可を使用すると、Keycloak で定義された一連のポリシーに基づいて、リソースとその特定の方法/アクションを保護できます。これにより、アプリケーションからの認可が外部化されます。これは、リソースを保護するために使用できる keycloak.enforcer メソッドを公開することで実現されます。

    app.get('/apis/me', keycloak.enforcer('user:profile'), userProfileHandler);

keycloak-enforcer メソッドは、response_mode 構成オプションの値に応じて、2 つのモードで動作します。

    app.get('/apis/me', keycloak.enforcer('user:profile', {response_mode: 'token'}), userProfileHandler);

response_modetoken に設定されている場合、アクセス許可は、アプリケーションに送信されたベアラートークンによって表されるサブジェクトに代わってサーバーから取得されます。この場合、新しいアクセストークンが、サーバーによって付与されたアクセス許可とともに Keycloak によって発行されます。サーバーが予期されるアクセス許可を持つトークンで応答しなかった場合、リクエストは拒否されます。このモードを使用する場合、次のようにリクエストからトークンを取得できるはずです。

    app.get('/apis/me', keycloak.enforcer('user:profile', {response_mode: 'token'}), function (req, res) {
        const token = req.kauth.grant.access_token.content;
        const permissions = token.authorization ? token.authorization.permissions : undefined;

        // show user profile
    });

アプリケーションがセッションを使用しており、サーバーからの以前の決定をキャッシュし、リフレッシュトークンを自動的に処理したい場合は、このモードを優先してください。このモードは、クライアントおよびリソースサーバーとして機能するアプリケーションに特に役立ちます。

response_modepermissions (デフォルトモード) に設定されている場合、サーバーは新しいアクセストークンを発行せずに、付与されたアクセス許可のリストのみを返します。新しいトークンを発行しないことに加えて、このメソッドは、次のように request を介してサーバーによって付与されたアクセス許可を公開します。

    app.get('/apis/me', keycloak.enforcer('user:profile', {response_mode: 'permissions'}), function (req, res) {
        const permissions = req.permissions;

        // show user profile
    });

使用中の response_mode に関係なく、keycloak.enforcer メソッドは、最初にアプリケーションに送信されたベアラートークン内のアクセス許可を確認しようとします。ベアラートークンがすでに予期されるアクセス許可を持っている場合、決定を取得するためにサーバーと対話する必要はありません。これは、クライアントが保護されたリソースにアクセスする前に、予期されるアクセス許可を持つアクセストークンをサーバーから取得できる場合に特に役立ちます。これにより、インクリメンタル認可などの Keycloak Authorization Services によって提供される一部の機能を使用でき、keycloak.enforcer がリソースへのアクセスを強制しているときにサーバーへの追加リクエストを回避できます。

デフォルトでは、ポリシーエンフォーサーは、アプリケーションに定義された client_id (たとえば、keycloak.json 経由) を使用して、Keycloak Authorization Services をサポートする Keycloak 内のクライアントを参照します。この場合、クライアントは実際にはリソースサーバーであるため、public にすることはできません。

アプリケーションがパブリッククライアント (フロントエンド) とリソースサーバー (バックエンド) の両方として機能している場合は、次の構成を使用して、強制するポリシーを持つ Keycloak 内の別のクライアントを参照できます。

      keycloak.enforcer('user:profile', {resource_server_id: 'my-apiserver'})

フロントエンドとバックエンドを表すために、Keycloak で個別のクライアントを使用することをお勧めします。

保護しているアプリケーションが Keycloak 認可サービスで有効になっており、keycloak.json でクライアントクレデンシャルを定義している場合は、追加のクレームをサーバーにプッシュして、ポリシーで使用できるようにして意思決定を行うことができます。そのためには、プッシュするクレームを含む JSON を返す function を予期する claims 構成オプションを定義できます。

      app.get('/protected/resource', keycloak.enforcer(['resource:view', 'resource:write'], {
          claims: function(request) {
            return {
              "http.uri": ["/protected/resource"],
              "user.agent": // get user agent  from request
            }
          }
        }), function (req, res) {
          // access granted

アプリケーションリソースを保護するように Keycloak を構成する方法の詳細については、認可サービスガイド を参照してください。

高度な認可

URL 自体の一部に基づいてリソースを保護するには (各セクションにロールが存在することを前提とします)

    function protectBySection(token, request) {
      return token.hasRole( request.params.section );
    }

    app.get( '/:section/:page', keycloak.protect( protectBySection ), sectionHandler );

高度なログイン構成

デフォルトでは、クライアントが bearer-only でない限り、すべての未承認リクエストは Keycloak ログインページにリダイレクトされます。ただし、confidential または public クライアントは、ブラウズ可能なエンドポイントと API エンドポイントの両方をホストする場合があります。認証されていない API リクエストでリダイレクトを防ぎ、代わりに HTTP 401 を返すには、redirectToLogin 関数をオーバーライドできます。

たとえば、このオーバーライドは、URL に /api/ が含まれているかどうかを確認し、ログインリダイレクトを無効にします。

    Keycloak.prototype.redirectToLogin = function(req) {
    const apiReqMatcher = /\/api\//i;
    return !apiReqMatcher.test(req.originalUrl || req.url);
    };

追加の URL

明示的なユーザー起動のログアウト

デフォルトでは、ミドルウェアは /logout への呼び出しをキャッチして、ユーザーを Keycloak 中心のログアウトワークフローに送ります。これは、middleware() 呼び出しに logout 構成パラメーターを指定することで変更できます。

    app.use( keycloak.middleware( { logout: '/logoff' } ));

ユーザー起動のログアウトが呼び出されると、クエリパラメーター redirect_url を渡すことができます。

https://example.com/logoff?redirect_url=https%3A%2F%2Fexample.com%3A3000%2Flogged%2Fout

このパラメーターは、OIDC ログアウトエンドポイントのリダイレクト URL として使用され、ユーザーは https://example.com/logged/out にリダイレクトされます。

Keycloak 管理コールバック

また、ミドルウェアは、単一のセッションまたはすべてのセッションをログアウトするための Keycloak コンソールからのコールバックをサポートしています。デフォルトでは、これらのタイプの管理コールバックは / のルート URL を基準にして発生しますが、middleware() 呼び出しに admin パラメーターを提供することで変更できます。

    app.use( keycloak.middleware( { admin: '/callbacks' } );

完全な例

Node.js アダプターの使用方法を示す完全な例は、Node.js 用 Keycloak クイックスタート にあります。

Node.js アダプターのアップグレード

Web アプリケーションにコピーされた Node.js アダプターをアップグレードするには、次の手順を実行します。

手順
  1. 新しいアダプターアーカイブをダウンロードします。

  2. 既存の Node.js アダプターディレクトリを削除します。

  3. 更新されたファイルをその場所に解凍します。

  4. アプリケーションの package.json で keycloak-connect の依存関係を変更します。

このページについて