LoginSignup
15
15

More than 1 year has passed since last update.

nginx を JavaScript でカスタマイズ : OpenID Connect リバースプロキシ

Last updated at Posted at 2023-04-16

nginx を用いたリバースプロキシ型の OpenID Connect(OIDC)ユーザ認証を、ローカルPCの開発環境向けに構築したので紹介します。

nginx の njs(NGINX JavaScript) を用いて OIDC 認可コードフローを実装しました。njs は nginx のモジュールです。JavaScript で nginx の機能を拡張できます。公式の nginx docker イメージでも利用できます。

nginx + JavaScript の組み合わせは、本番間環境により近い構成にできる自由度の高さがメリットです。

リバースプロキシ型とは

ユーザー認証をリバースプロキシと認可サーバに任せます。アプリケーションはビジネスロジックの開発に集中できます。

リバースプロキシは、ブラウザからの HTTP リクエストをバックエンドの WEBサーバへ転送しますがその際、HTTPリクエストのセッション Cookie を検証します。

セッション Cookie が存在しない場合、リバースプロキシは、認可サーバへリダイレクトしユーザにログインを促します。ユーザがログインするとリバースプロキシへリダイレクトバックされるので、認可サーバからトークンを取得しそのトークンを用いてユーザ情報を取得するとともにセッション Cookie を作成します。

セッション Cookie が存在する場合、リバースプロキシは、HTTPリクエストヘッダにユーザ情報を付加し WEBサーバへ転送します。

リバースプロキシ型について詳しくは次の記事などを参照ください。

njs で実装した機能

njs を用いると、WEBサーバが HTTP リクエストハンドラを呼び出すのと似た感じで nginx から JavaScript 関数を呼び出すことができます。

ただし njs は、nginx をアプリケーション サーバーにすることが目的ではないとのこと ↓

今回は、njs で次の機能を実装しました。

  • OIDC 認可コードフローでトークン取得
  • 認可サーバからユーザ情報を取得し JWT に変換後、セッション Cookie に格納
  • セッション Cookie の JWT を検証

注意
OIDC の state や nonce といった脆弱性対策は実装していません

動作環境

Dockerコンテナで環境構築します。

  • Docker version 23.0
  • 認可サーバ: Keycloak 21.0
  • リバースプロキシ: nginx 1.23
  • WEBサーバ: nginx 1.23

実行方法

次のフォルダ構成で各種の設定ファイルと njs の JavaScript コードを配置し docker compose を実行します。設定ファイルおよびコードの全文は以降に掲載します。

フォルダ構成

.
├── backend/                      # WEBサーバ
│   └── nginx.conf
├── docker-compose.yml
├── keycloak/                     # 認可サーバ
│   └── import/
│       └── realm-develop.json    # Keycloak の Realm エクスポートファイル
└── proxy/                        # リバースプロキシ
    ├── conf.d/
    │   └── app.conf
    ├── nginx.conf
    └── njs/                      # njs の JavaScript コード
        ├── jwt.js
        └── oidc.js
docker-compose.yml
version: "3.9"
services:
  keycloak:
    image: quay.io/keycloak/keycloak:21.0
    container_name: idp
    restart: always
    command: start-dev --import-realm
    environment:
      KEYCLOAK_ADMIN: "admin"
      KEYCLOAK_ADMIN_PASSWORD: "admin"
      KC_HOSTNAME_URL: "http://localhost:8080"
      KC_HOSTNAME_STRICT_BACKCHANNEL: false
    ports:
      - 0.0.0.0:8080:8080
    volumes:
      - ./keycloak/import:/opt/keycloak/data/import/:ro
  proxy:
    container_name: proxy
    image: nginx:1.23
    restart: always
    environment:
      OIDC_ISSUER: "http://localhost:8080/realms/develop"
      OIDC_AUTH_ENDPOINT: "http://localhost:8080/realms/develop/protocol/openid-connect/auth"
      OIDC_LOGOUT_ENDPOINT: "http://localhost:8080/realms/develop/protocol/openid-connect/logout"
      OIDC_TOKEN_ENDPOINT: "http://idp:8080/realms/develop/protocol/openid-connect/token"
      OIDC_USER_INFO_ENDPOINT: "http://idp:8080/realms/develop/protocol/openid-connect/userinfo"
      OIDC_CLIENT_ID: "reverse-proxy"
      OIDC_CLIENT_SECRET: "hogehogehoge"
      OIDC_SCOPE: "openid" # 空白文字区切り
      JWT_GEN_KEY: "mySecretKey"
    ports:
      - 0.0.0.0:80:80
    volumes:
      - ./proxy/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./proxy/conf.d:/etc/nginx/conf.d/:ro
      - ./proxy/njs:/etc/nginx/njs/:ro
  backend:
    container_name: backend
    image: nginx:1.23
    volumes:
      - ./backend/nginx.conf:/etc/nginx/nginx.conf:ro

コンテナを開始します。

$ docker compose up -d
$ docker compose ps
NAME      IMAGE                           ...  SERVICE   ...  PORTS
backend   nginx:1.23                      ...  backend   ...  80/tcp
idp       quay.io/keycloak/keycloak:21.0  ...  keycloak  ...  0.0.0.0:8080->8080/tcp, 8443/tcp
proxy     nginx:1.23                      ...  proxy     ...  0.0.0.0:80->80/tcp

コンテナを開始すると Keycloak の初期化処理が走るのでそれが終わるまで1分ほど待ちます。

ブラウザで http://localhost/ を開くと Keycloak のログイン画面が表示されます。初回はユーザ登録(セルフサインアップ)します。ログインするとバックエンドのWEBサーバ nginx のウェルカムページが表示されます。

qt_1_1.png

ブラウザの開発ツールで、セッション Cookie MY_SESSION が作成されていることを確認。

qt_1_9.png

WEBサーバ nginx(backendコンテナ)のログで、HTTPリクエストヘッダにユーザ情報(JWT)が付加されていることを確認。ログの eyJ0eX ... CnARo の部分がユーザ情報の JWT です。

$ docker compose logs backend
backend  | 192.168.208.2 - - [05/Apr/2023:09:42:58 +0000] "GET / HTTP/1.0" 200 615 "-" "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI1YjhiNWZjZS1jN2JjLTQ2NmMtOTJlMy0wOTQxYTc0NGJmNzkiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm5hbWUiOiJhIGIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJkZXZAZXhhbXBsZS5jb20iLCJnaXZlbl9uYW1lIjoiYSIsImZhbWlseV9uYW1lIjoiYiIsImVtYWlsIjoiZGV2QGV4YW1wbGUuY29tIn0.Hhl9aBQznkfQTjjShyR1-5kkxYFvdXtKVAQgXjCnARo"

サイト JSON Web Tokens - jwt.io で JWT の中身を確認できます。漏洩しても問題が無いデータでお試しください。

qt_1_10.png

ブラウザで http://localhost/logout を叩くとログアウトできます。

qt_1_2.png

リバースプロキシ nginx の設定

proxy/nginx.conf に次の設定を追加します。

  • njs モジュールをロード
  • conf.d/app.conf をインクルード
proxy/nginx.conf
user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;

load_module modules/ngx_http_js_module.so;      # njs モジュールをロード

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/app.conf;     # conf.d/app.conf をインクルード
}

proxy/conf.d/app.conf に次を設定します。

  • Docker resolver
  • インポートするJSモジュールのパス追加
  • インポートするJSモジュール
  • nginx 変数に JS関数の戻り値を設定
  • server セクションで URLパスと JSモジュール関数を紐づけ
proxy/conf.d/app.conf
resolver 127.0.0.11;                # Docker resolver

js_path "/etc/nginx/njs/";          # インポートするJSモジュールのパス追加
js_import oidc from oidc.js;        # インポートするJSモジュール

js_set $user_info oidc.user_info;   # nginx 変数に JS関数の戻り値を設定

server {
    listen 80;

    location / {
        auth_request /auth/validate;                   # /auth/validate でログイン有無を確認
        error_page 401 = @login;                       # 未ログインなら @login へ
        expires -1;
        proxy_set_header X-Amzn-Oidc-Data $user_info;  # ヘッダーにユーザ情報を付加
        proxy_pass http://backend;                     # WEBサーバへ転送
    }

    location /auth/validate {
        internal;
        js_content oidc.validate;           # JSモジュール oidc の関数 validate を呼び出し
    }

    location @login {
        js_content oidc.login;              # JSモジュール oidc の関数 login を呼び出し
    }

    location /auth/postlogin {
        js_content oidc.postlogin;          # JSモジュール oidc の関数 postlogin を呼び出し
    }

    location /logout {
        js_content oidc.logout;             # JSモジュール oidc の関数 logout を呼び出し
    }

    location /auth/postlogout {
        js_content oidc.postlogout;         # JSモジュール oidc の関数 postlogout を呼び出し
    }

    location @bye {
        add_header 'Content-Type' 'text/html';
        return 200 "See you again.\n";
    }
}

ロケーション / にマッチすると /auth/validate にログイン有無を確認しにいきます。/auth/validate は、JS関数 oidc.validate() を呼び出します。oidc.validate() は、セッション Cookie を調べログイン済みなら HTTPステータス 200 を返し、未ログインなら 401 を返します。ステータス 200 ならヘッダーにユーザ情報を付加しリクエストをバックエンドへ転送します。401 なら @login へ内部リダイレクトし JS関数 oidc.login() を呼び出しログインシーケンスを開始します。ログインシーケンスは OIDC 認可コードフローに基づきます。

qt_1_11.png

@ で始まるロケーションは、nginx の名前付きロケーションです。URIパスの書き換えを行わない内部リダイレクトに利用します。

リバースプロキシ nginx の JS関数

ロケーション /auth/validate に紐づく JS関数 oidc.validate() は、セッション Cookie に格納された JWT の署名を検証します。検証に成功すれば HTTPステータス 200 を、失敗すれば 401 を返します。

  • JS関数 oidc.validate()
proxy/njs/oidc.js(抜粋)
// ログイン済みか否かをチェック
async function validate(r) {
    let secret_key = process.env.JWT_GEN_KEY;
    let session_data = r.variables.cookie_MY_SESSION;
    let valid = await jwt.verify(session_data, secret_key);
    if( valid ) {
        r.return(200);
    } else {
        r.return(401);
    }
}

ここで関数 validate(r) の引数 r は、HTTPリクエストを表すオブジェクトです。process は、プロセスに関する情報を提供するグローバル オブジェクトです。

リファレンスはこちら↓

process.env.JWT_GEN_KEY は、環境変数 JWT_GEN_KEY の値を取得します。

リファレンスの注釈に次のようにありますが、動作確認した環境では env ディレクティブを使用せずとも環境変数を参照できました。理由は不明。

デフォルトでは、nginx は親プロセスから継承されたすべての環境変数を削除しますが、TZ 変数は除きます。継承された変数の一部を保持するには、 env ディレクティブを使用します。

r.variables.cookie_MY_SESSIONnginx の組み込み変数です。Cookie MY_SESSION の値を参照します。このように JSコード内で nginx の組み込み変数を参照することもできます。

ロケーション @login に紐づくJS関数 oidc.login() は、認可サーバ(Keycloak)の認可エンドポイントへリダイレクトします。認可サーバは、ログイン画面を表示します。

  • JS関数 oidc.login()
proxy/njs/oidc.js(抜粋)
const postlogin_uri = "http://localhost:80/auth/postlogin";


// OIDC認可エンドポイントへリダイレクト
function login(r) {
    let referer = r.variables.uri;
    let params = qs.stringify({
        response_type : "code",
        scope         : process.env.OIDC_SCOPE,
        client_id     : process.env.OIDC_CLIENT_ID,
        redirect_uri  : postlogin_uri + "?" + qs.stringify({p: referer}),
    });
    let url = process.env.OIDC_AUTH_ENDPOINT + "?" + params;
    r.return(302, url);
}

ユーザがログインすると、認可エンドポイントへのリクエストパラメータに指定した redirect_uri(= /auth/postlogin)へリダイレクトバックされ、JS関数 oidc.postlogin() が呼び出されます。

JS関数 oidc.postlogin() は、認可サーバのトークンエンドポイントから IDトークンを取得し検証します。そのトークンを用いてユーザ情報を取得し JWT へ変換します。JWT をセッション Cookie に設定し、呼び出し元ページへリダイレクトします。

  • JS関数 oidc.postlogin()
proxy/njs/oidc.js(抜粋)
// ログイン後の処理
async function postlogin(r) {
    try {
        let referer = r.args.p;

        // OIDCトークン取得
        let redirect_uri = postlogin_uri + "?" + qs.stringify({p: referer});
        let tokens = await get_token(r.args.code, redirect_uri);

        // IDトークン検証
        validate_id_token(tokens.id_token, process.env.OIDC_ISSUER, process.env.OIDC_CLIENT_ID);

        // OIDCユーザ情報取得
        let claims = await get_userinfo(tokens.access_token);

        // ユーザ情報を JWT に変換
        let secret_key = process.env.JWT_GEN_KEY;
        let session_data = await jwt.encode(claims, secret_key);

        r.headersOut["Set-Cookie"] = [
            "MY_SESSION=" + session_data + "; Path=/",
        ];
        r.return(302, referer);
    }  catch (e) {
        r.error(e.message);
        r.return(403);  // Forbidden
    }
}

注意
IDトークンの検証は、一部のクレームのみを対象とし、署名は検証していません。

proxy/njs/oidc.js(抜粋)
// OIDC IDトークン検証
function validate_id_token(token, issuer, audience) {
    let payload = jwt.decode(token).payload;
    if( payload.iss != issuer ) throw new Error("invalid token");
    if( payload.aud != audience ) throw new Error("invalid token");
    if( payload.exp < Math.floor(Date.now()/1000) ) throw new Error("invalid token");
}

セッション Cookie に格納する JWT は暗号化していません。
暗号化したい場合は njs のサンプルコードに暗号化の例があります。

  • njs サンプルコード

ログアウトの際は、認可サーバ Keycloak のセッションも併せて終了する必要があります。

ロケーション /logout に紐づく JS関数 oidc.logout() は、Keyclok のログアウトエンドポイントへリダイレクトします。Keycloak がログアウトされるとロケーション/postlogout へリダイレクトバックされるので JS関数 oidc.postlogout() でセッション Cookie を削除します。

  • JS関数 oidc.logout()
proxy/njs/oidc.js(抜粋)
// ログアウトエンドポイントへリダイレクト
function logout(r) {
    let params = qs.stringify({
        client_id : process.env.OIDC_CLIENT_ID,
        post_logout_redirect_uri : postlogout_uri,
    });
    let url = process.env.OIDC_LOGOUT_ENDPOINT + "?" + params;
    r.return(302, url);
}
  • JS関数 oidc.postlogout()
proxy/njs/oidc.js(抜粋)
// ログアウト後の処理. セッション Cookie 削除
function postlogout(r) {
    r.headersOut['Set-Cookie'] = ["MY_SESSION=; Path=/; Max-Age=-1; Expires=Wed, 21 Oct 2015 07:28:00 GMT"];
    r.internalRedirect("@bye");
}

バックエンド nginx の設定

バックエンドのWEBサーバやアプリケーションサーバは、リバースプロキシが HTTPリクエストヘッダに付加したユーザ情報(JWT)を見て、それが誰からのリクエストなのかを識別し、リソースのアクセス制御を行うことができます。

ここではそこまで実装しません。HTTPリクエストヘッダに付加されたユーザ情報(JWT)をログ出力するまでにとどめます。

backend/nginx.conf に次を設定します。

  • log_format にHTTPリクエストヘッダの出力を追加
nginx;backend/nginx.conf
user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    # ユーザ情報(ヘッダ X-Amzn-Oidc-Data)をログへ出力
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_x_amzn_oidc_data"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}

ここでは、ユーザ情報(JWT)をヘッダ X-Amzn-Oidc-Data に格納しているので log_format'"$http_x_amzn_oidc_data"' を追加します。

ログ出力例

$ docker compose logs backend
backend  | 192.168.208.2 - - [05/Apr/2023:09:42:58 +0000] "GET / HTTP/1.0" 200 615 "-" "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI1YjhiNWZjZS1jN2JjLTQ2NmMtOTJlMy0wOTQxYTc0NGJmNzkiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsIm5hbWUiOiJhIGIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJkZXZAZXhhbXBsZS5jb20iLCJnaXZlbl9uYW1lIjoiYSIsImZhbWlseV9uYW1lIjoiYiIsImVtYWlsIjoiZGV2QGV4YW1wbGUuY29tIn0.Hhl9aBQznkfQTjjShyR1-5kkxYFvdXtKVAQgXjCnARo"

このヘッダ名にした理由が気になる方は、巻末の参考記事「Application Load Balancer を使用してユーザーを認証する」をご覧ください。

Keycloak の Realm エクスポートファイルの準備

リバースプロキシが認可サーバからトークンを取得する際にクライアント ID とクライアントシークレットが必要ですが、そのためには Keycloak の Realm にアプリケーションクライアントを登録する必要があります。

コンテナを開始/終了する度にアプリケーションクライアントを登録しなおすのは面倒なので、事前定義した Realm をインポートするようにします。

注意
クライアントシークレットの安全な取り扱いについては考慮しません。

手順

  1. Realm 作成
  2. アプリケーションクライアント登録
  3. セルフサインアップ有効化
  4. Realm エクスポート
  5. クライアントシークレット設定
  6. Realm エクスポートファイル配置

Realm エクスポートファイル keycloak/import/realm-develop.json が存在しない状態で Docker Compose を実行しコンテナを開始します。

$ docker compose up -d

Keycloak 初期化処理が終わるまで1分ほど待ちます。ブラウザから http://localhost:8080/ を開くと Keycloak のウェルカムページが表示されるので [Administration Console] を選択し admin/admin でログインします。

qt_1_3.png

Realm 作成

Realm "develop" を作成します。

画面左上の master と表示されているプルダウンから Create Realm を選択。
Realm name に "develop" と入力し Create する。

qt_1_4.png

アプリケーションクライアント登録

作成したRealm "develop" にアプリケーションクライアント "reverse-proxy" を登録します。

サイドメニューから Clients を選択し Creat Client をクリック。

qt_1_5.png

必須入力項目

Field Value
Client ID reverse-proxy
Client authentication ON
Valid redirect URIs *
Valid post logout redirect URIs *

注意
上記の redirect URIs の設定値は全てを許容する脆弱な設定です。

セルフサインアップ有効化

ユーザーのセルフサインアップを許可します。

サイドメニューから Realm settings を選択。
タブ Login を開き User registration を ON。

qt_1_6.png

Realm エクスポート

作成した Realm "develop" をファイルにエクスポートします。

画面右上の Action プルダウンから Partial Export を選択。

qt_1_6.png

Include clients を ON にし Export を実行。

qt_1_7.png

クライアントシークレット設定

エクスポートされたファイルにクライアントシークレットは出力されないので、手動でクライアントシークレットを書き込みます。

保存した Realm エクスポートファイルをエディタで開く。
clientId "reverse-proxy" を検索し secret に "hogehogehoge" と入力し保存。

qt_1_8.png

Realm エクスポートファイル配置

Realm エクスポートファイルを keycloak/import/realm-develop.json に配置します。

Docker Compose を再起動すると Realm エクスポートファイルがインポートされます。

$ docker compose down -v

$ docker compose up -d

コンテナ keycloan のログにインポートした旨のメッセージが表示されます。

$ docker compose logs keycloak

... Full importing from file /opt/keycloak/bin/../data/import/realm-develop.json
... Realm 'develop' imported

Realm のインポート/エクスポートについては、Keycloak のドキュメントを参照ください ↓

njs コード全文

proxy/njs/jwt.js

proxy/njs/jwt.js
// JWTデコード
function decode(jot) {
    var parts = jot.split('.').slice(0,2)
        .map(v=>Buffer.from(v, 'base64url').toString())
        .map(JSON.parse);
    return { headers:parts[0], payload: parts[1] };
}

// JWTエンコード
async function encode(claims, key) {
    let header = { typ: "JWT",  alg: "HS256" };

    let s = [header, claims]
        .map(JSON.stringify)
        .map(v=>Buffer.from(v).toString('base64url'))
        .join('.');

    let wc_key = await crypto.subtle.importKey(
        'raw', key, {name: 'HMAC', hash: 'SHA-256'}, false, ['sign']);

    let sign = await crypto.subtle.sign({name: 'HMAC'}, wc_key, s);

    return s + '.' + Buffer.from(sign).toString('base64url');
}

// JWT署名検証
async function verify(jot, key) {
    if( !jot ) return false;

    let parts = jot.split('.');
    let data = parts.slice(0,2).join('.');
    let sign = Buffer.from(parts[2], 'base64url');
    let wc_key = await crypto.subtle.importKey(
        'raw', key, {name: 'HMAC', hash: 'SHA-256'}, false, ['verify']);

    return crypto.subtle.verify({name: 'HMAC'}, wc_key, sign, data);
}

export default {decode, encode, verify}

proxy/njs/oidc.js

proxy/njs/oidc.js
import qs from "querystring";
import jwt from "jwt.js";

const postlogin_uri = "http://localhost:80/auth/postlogin";
const postlogout_uri = "http://localhost:80/auth/postlogout";

// OIDC IDトークン検証
function validate_id_token(token, issuer, audience) {
    let payload = jwt.decode(token).payload;
    if( payload.iss != issuer ) throw new Error("invalid token");
    if( payload.aud != audience ) throw new Error("invalid token");
    if( payload.exp < Math.floor(Date.now()/1000) ) throw new Error("invalid token");
}

// OIDCトークンエンドポイントからトークン取得
async function get_token(code, redirect_uri) {
    let reply = await ngx.fetch(process.env.OIDC_TOKEN_ENDPOINT, {
        method: "POST",
        headers: { "Content-Type": "application/x-www-form-urlencoded" },
        body: qs.stringify({
            grant_type    : "authorization_code",
            client_id     : process.env.OIDC_CLIENT_ID,
            client_secret : process.env.OIDC_CLIENT_SECRET,
            redirect_uri  : redirect_uri,
            code          : code,
        }),
    });
    return await reply.json();
}

// OIDCユーザ情報取得
async function get_userinfo(access_token) {
    let reply = await ngx.fetch(process.env.OIDC_USER_INFO_ENDPOINT, {
        method: "GET",
        headers: { "Authorization": "Bearer " + access_token },
    });
    return await reply.json();
}

// ログイン済みか否かをチェック
async function validate(r) {
    let secret_key = process.env.JWT_GEN_KEY;
    let session_data = r.variables.cookie_MY_SESSION;
    let valid = await jwt.verify(session_data, secret_key);
    if( valid ) {
        r.return(200);
    } else {
        r.return(401);
    }
}

// OIDC認可エンドポイントへリダイレクト
function login(r) {
    let referer = r.variables.uri;
    let params = qs.stringify({
        response_type : "code",
        scope         : process.env.OIDC_SCOPE,
        client_id     : process.env.OIDC_CLIENT_ID,
        redirect_uri  : postlogin_uri + "?" + qs.stringify({p: referer}),
    });
    let url = process.env.OIDC_AUTH_ENDPOINT + "?" + params;
    r.return(302, url);
}

// ログイン後の処理
async function postlogin(r) {
    try {
        let referer = r.args.p;

        // OIDCトークン取得
        let redirect_uri = postlogin_uri + "?" + qs.stringify({p: referer});
        let tokens = await get_token(r.args.code, redirect_uri);

        // IDトークン検証
        validate_id_token(tokens.id_token, process.env.OIDC_ISSUER, process.env.OIDC_CLIENT_ID);

        // OIDCユーザ情報取得
        let claims = await get_userinfo(tokens.access_token);

        // ユーザ情報を JWT に変換
        let secret_key = process.env.JWT_GEN_KEY;
        let session_data = await jwt.encode(claims, secret_key);

        r.headersOut["Set-Cookie"] = [
            "MY_SESSION=" + session_data + "; Path=/",
        ];
        r.return(302, referer);
    }  catch (e) {
        r.error(e.message);
        r.return(403);  // Forbidden
    }
}

// ログアウトエンドポイントへリダイレクト
function logout(r) {
    let params = qs.stringify({
        client_id : process.env.OIDC_CLIENT_ID,
        post_logout_redirect_uri : postlogout_uri,
    });
    let url = process.env.OIDC_LOGOUT_ENDPOINT + "?" + params;
    r.return(302, url);
}

// ログアウト後の処理. セッション Cookie 削除
function postlogout(r) {
    r.headersOut['Set-Cookie'] = ["MY_SESSION=; Path=/; Max-Age=-1; Expires=Wed, 21 Oct 2015 07:28:00 GMT"];
    r.internalRedirect("@bye");
}

// バックエンドへ送るユーザ情報
function user_info(r) {
    return r.variables.cookie_MY_SESSION;
}

export default {validate, login, logout, postlogin, postlogout, user_info}

参考にした記事

15
15
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
15