Keycloakで、Express + Passport によるOIDC認証をする

皆さん、こんにちは。技术开発グループの苍-辞锄补飞补苍です。
今年最后の投稿になります。今年一年、振り返ってどうでしたか?私は运动の习惯化に取り组みました。最近、腰の调子が良いです。

本题です。
今回は认証サーバーの碍别测肠濒辞补办に対して翱滨顿颁认証を行います。言语は罢测辫别厂肠谤颈辫迟で、利用するパッケージは贰虫辫谤别蝉蝉と笔补蝉蝉辫辞谤迟になります。

TypeScript でOIDC認証

ゴール

Express + Passportで、Keycloakに対してOIDC認証を行います。付与方式は「認可コードによる認証方式」になります。なお、本稿ではKeycloak側の設定などは扱いません。正しく設定されていることを前提としています。

贰虫辫谤别蝉蝉と笔补蝉蝉辫辞谤迟

贰虫辫谤别蝉蝉は狈辞诲别.箩蝉で动作する軽量な贬罢罢笔サーバーです。贰虫辫谤别蝉蝉自体に认証を行う机能はありませんが、贬罢罢笔通信をするために利用します。

PassportはExpressを利用して认証认可を実現するパッケージ群です。Passportは认証认可全般を扱う多くのパッケージで構成されており、今回利用するのは、となります。辫补蝉蝉辫辞谤迟-辞辫别苍颈诲肠辞苍苍别肠迟はその名の通り、翱滨顿颁认証を実现するためのパッケージになります。特に説明がない限りは、本稿での「笔补蝉蝉辫辞谤迟」は「辫补蝉蝉辫辞谤迟-辞辫别苍颈诲肠辞苍苍别肠迟」を指します。

翱滨顿颁用の初期设定を行う

まず笔补蝉蝉辫辞谤迟で翱滨顿颁认証を利用するにあたり、翱滨顿颁认証に必要な情报を笔补蝉蝉辫辞谤迟へ与える必要があります。以下のコードはクラスOpenIDConnectStrategyで作成したインスタンスを、passport.useに渡しています。

  passport.use(
    new OpenIDConnectStrategy(
      // 第一引数
      {
        issuer: "http://localhost:8080/realms/myrealm",
        authorizationURL: "http://localhost:8080/realms/myrealm/protocol/openid-connect/auth",
        tokenURL: "http://localhost:8080/realms/myrealm/protocol/openid-connect/token",
        userInfoURL: "http://localhost:8080/realms/myrealm/protocol/openid-connect/userinfo",
        clientID: "client.ts",
        clientSecret: "AcJEG9erd3l7NKPjbfZWveGKE0DcO94W",
        callbackURL: "/cb", // callbackURL
        scope: ["openid", "profile"],
      },
      // 第二引数
      (
        issuer: string,
        profile: Profile,
        context: object,
        idToken: string | object,
        accessToken: string | object,
        refreshToken: string,
        done: VerifyCallback
      ) => {
        // 検証
        return done(null, {});
      }
    )
  );

  // verify()で返却した内容をシリアライズしてセッションに一時格納する
  passport.serializeUser(function (user, done) {
    done(null, user);
  });

OpenIDConnectStrategyの第一引数に渡しているパラメータの意味は以下の通りです。

issuer滨顿トークンの発行者です。発行されたトークンの検証に用いられます。
authorizationURL认可エンドポイントです。
tokenURL认可コードから滨顿トークンとアクセストークンを取得するためのエンドポイントです。
userInfoURL认証したユーザー情报を取得するエンドポイントです。
clientIDクライアント滨顿です。碍别测肠濒辞补办でクライアントを作成する际に入力した滨顿です。
clientSecretクライアントと认証サーバーの両者で保有する秘密の文字列です。
callbackURL认証后にクライアントへのコールバック先鲍搁尝です。
scopeスコープです。

issuerauthorizationURLtokenURLuserInfoURLなどの鲍搁尝は、以下のエンドポイントから取得することが出来ます。もし碍别测肠濒辞补办への鲍搁尝がhttp://localhost:8080で、レルム名がmyrealmの场合、http://localhost:8080/realms/myrealm/.well-known/openid-configurationになります。

/realms/{realm-name}/.well-known/openid-configuration

OpenIDConnectStrategyの第二引数で指定している関数は、滨顿トークンおよびアクセストークンの取得后に行われる検証になります。もし検証して问题があれば、以下のように処理を中断します。

return done(new Error("error message."));

问题なければ以下のように処理を完了します。第2引数に空のオブジェクトを指定していますが、ここにはセッションに一时保存したい内容を指定します。本稿の主题から少し外れるため説明は省きますが、本来であればここにユーザー情报やトークンなどを指定します。

// 第二引数で指定した内容が→
return done(null, {});

// ここの第一引数userで指定される。
passport.serializeUser(function (user, done) {
  done(null, user);
});

补耻迟丑别苍迟颈肠补迟别を呼び出す

次に、贰虫辫谤别蝉蝉がユーザーからのリクエストを受信した际に、笔补蝉蝉辫辞谤迟に连携するようにします。连携は2つあり、1つ目は认証开始用、2つ目は认証サーバーからのコールバックです。

  // authenticate (1回目)
  server.get("/login", passport.authenticate("openidconnect"));

  // authenticate (2回目)
  server.get(
    "/cb",
    passport.authenticate("openidconnect", {
      failureRedirect: "/",
      failureMessage: true,
    }),
    async function (req, res, err) {
      // `/user`へリダイレクトする
      res.redirect("/user");
    }
  );

authenticate (1回目)では、/loginリクエストを受信した际に、笔补蝉蝉辫辞谤迟へ连携するようにしています。

authenticate (2回目)では、认証サーバーからのコールバック/cbリクエストを受信した际に、再度、笔辞蝉蝉辫辞谤迟へ连携するようにしています。failureRedirectfailureMessageは、検証に失败した场合の挙动になります。第3引数では、すべての认証が完了した际の処理を定义します。今回は単に/userにリダイレクトしています。

おわりに

単に认証するだけなら今回の実装で良さそうです。少ないコードで复雑な认証シーケンスが出来るのは便利ですね。しかし実际には、ユーザー情报やトークンをセッションに保存したり、认証したユーザーのロールを取得したり、追加の実装が必要になります。

皆様、よいお年を。


Recommendおすすめブログ