Keycloakで、Express + Passport によるOIDC認証をする
皆さん、こんにちは。技術開発グループのn-ozawanです。
今年最後の投稿になります。今年一年、振り返ってどうでしたか?私は運動の習慣化に取り組みました。最近、腰の調子が良いです。
本題です。
今回は認証サーバーのKeycloakに対してOIDC認証を行います。言語はTypeScriptで、利用するパッケージはExpressとPassportになります。
目次
TypeScript でOIDC認証
ゴール
Express + Passportで、Keycloakに対してOIDC認証を行います。付与方式は「認可コードによる認証方式」になります。なお、本稿ではKeycloak側の設定などは扱いません。正しく設定されていることを前提としています。

ExpressとPassport
ExpressはNode.jsで動作する軽量なHTTPサーバーです。Express自体に認証を行う機能はありませんが、HTTP通信をするために利用します。
PassportはExpressを利用して認証認可を実現するパッケージ群です。Passportは認証認可全般を扱う多くのパッケージで構成されており、今回利用するのは、passport-openidconnectとなります。passport-openidconnectはその名の通り、OIDC認証を実現するためのパッケージになります。特に説明がない限りは、本稿での「Passport」は「passport-openidconnect」を指します。
OIDC用の初期設定を行う
まずPassportでOIDC認証を利用するにあたり、OIDC認証に必要な情報を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 | IDトークンの発行者です。発行されたトークンの検証に用いられます。 |
authorizationURL | 認可エンドポイントです。 |
tokenURL | 認可コードからIDトークンとアクセストークンを取得するためのエンドポイントです。 |
userInfoURL | 認証したユーザー情報を取得するエンドポイントです。 |
clientID | クライアントIDです。Keycloakでクライアントを作成する際に入力したIDです。 |
clientSecret | クライアントと認証サーバーの両者で保有する秘密の文字列です。 |
callbackURL | 認証後にクライアントへのコールバック先URLです。 |
scope | スコープです。 |
issuer
、authorizationURL
、tokenURL
、userInfoURL
などのURLは、以下のエンドポイントから取得することが出来ます。もしKeycloakへのURLがhttp://localhost:8080
で、レルム名がmyrealm
の場合、
になります。http://localhost:8080
/realms/myrealm
/.well-known/openid-configuration
/realms/{realm-name}/.well-known/openid-configuration
OpenIDConnectStrategy
の第二引数で指定している関数は、IDトークンおよびアクセストークンの取得後に行われる検証になります。もし検証して問題があれば、以下のように処理を中断します。
return done(new Error("error message."));
問題なければ以下のように処理を完了します。第2引数に空のオブジェクトを指定していますが、ここにはセッションに一時保存したい内容を指定します。本稿の主題から少し外れるため説明は省きますが、本来であればここにユーザー情報やトークンなどを指定します。
// 第二引数で指定した内容が→
return done(null, {});
// ここの第一引数userで指定される。
passport.serializeUser(function (user, done) {
done(null, user);
});
authenticateを呼び出す
次に、Expressがユーザーからのリクエストを受信した際に、Passportに連携するようにします。連携は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
リクエストを受信した際に、Passportへ連携するようにしています。
authenticate (2回目)
では、認証サーバーからのコールバック/cb
リクエストを受信した際に、再度、Possportへ連携するようにしています。failureRedirect
やfailureMessage
は、検証に失敗した場合の挙動になります。第3引数では、すべての認証が完了した際の処理を定義します。今回は単に/user
にリダイレクトしています。
おわりに
単に認証するだけなら今回の実装で良さそうです。少ないコードで複雑な認証シーケンスが出来るのは便利ですね。しかし実際には、ユーザー情報やトークンをセッションに保存したり、認証したユーザーのロールを取得したり、追加の実装が必要になります。
皆様、よいお年を。