Go で OIDC RP が実装できるライブラリを探す
はじめに
OIDC RP を実装する場合以下のようなことをする必要があります。 ※ 認可コードグラントです。
- ディスカバリ構成の管理
- 認可エンドポイントの生成
- トークンエンドポイントへのアクセス
- トークンの検証
etc …
OIDC はカッチリ仕様が決まっていますし、そこまで複雑な実装にもならないと思いますが、ライブラリがあるなら頼りたいものです。 本記事では Go で OIDC RP をライブラリを用いて実装するにあたり候補となるライブラリを調査し実際に使ってみたのでご紹介します。
ライブラリ
調べたところ候補となるライブラリは以下の2つがありました。 (ほかにもあったら教えていただきたいです。)
https://github.com/zitadel/oidc
https://github.com/coreos/go-oidc
どちらもライセンスは Apache-2.0 license です。
本記事執筆で参照しているそれぞれのライブラリのバージョンは
です。
実装フロー
今回ライブラリを使ってみるにあたり実装したフローは以下となります。 説明のため、一部パラメータなどを省略しているので正確なものではありません。 ご了承ください。
sequenceDiagram
autonumber
participant web as ブラウザ
participant rp as Relying Party
participant op as OpenID Provider
web ->> rp: GET /
rp -->> web: ログイン画面
web ->> rp: GET /auth
rp -->> web: 302
web ->> op: GET /authorization_endpoint?state=xxx&redirect_uri=/callback
Note over web, op: SSO ログイン
op -->> web: 302
web ->> rp: GET /callback?state=xxx&code=xxx
rp ->> op: POST /token_endpoint
Note left of op: code
op -->> rp: 200
Note right of rp: access_token, refresh_token, id_token
rp ->> rp: id_token verify
rp -->> web: 200
Note right of web: html
Go によるサーバーでの Relying Party の実装なので nonce や PKCE は利用しません。
(あと、この方がよりシンプルに説明できるのかなと思っています。)
OpenAPI でエンドポイントを定義して利用します。
サーバーの実装をサボるために ogen による自動生成を活用しています。
準備
実際に動作確認を実施するにあたり Google と連携します。
https://developers.google.com/identity/openid-connect/openid-connect?hl=ja
こちらのドキュメントを参考にプロジェクトを用意しています。
coreos/go-oidc
coreos/go-oidc は以下に示すように example が用意されています。
https://github.com/coreos/go-oidc/tree/v3/example
パッケージインポート
import(
"github.com/coreos/go-oidc/v3/oidc"
"golang.org/x/oauth2"
)
初期化処理
一連の処理にて oidc.Provider と oauth2.Config という構造体が必要となります。
ctx := context.Background()
provider := oidc.NewProvider(ctx, "issuer")
config := &oauth2.Config{
ClientID: "client_id",
ClientSecret: "client_secret",
Endpoint: provider.Endpoint(),
RedirectURL: "redirect_url",
Scopes: []string{oidc.ScopeOpenID}
}
認可エンドポイント生成処理
// GET /auth へのアクセス
endpoint := config.AuthCodeURL("state")
// 上記で生成したエンドポイントにリダイレクトされるようにクライアントに返す
トークン取得処理
// GET /callback へのアクセス
token, _ := config.Exchange(ctx, "code")
idToken, _ := token.Extra("id_token").(string)
IDトークン検証処理
if _, err := provider.Verifier(&oidc.Config{
ClientID: "client_id"
}).Verify(ctx, idToken); err != nil {
panic(err)
}
zitadel/oidc
zitadel/oidc でも以下に示すように example が用意されています。
https://github.com/zitadel/oidc/tree/main/example
こちらのサンプルコードでは処理を http.HandleFunc() へと渡す形式で実装されています。
が、ogen などのライブラリを活用して実装する場合だと参考になりません。
コードを漁っていると他の方法でも OIDC RP としての処理を記述できることがわかったのでそちらの方法で実装します。
パッケージインポート
import (
"github.com/zitadel/oidc/v3/pkg/client/rp"
"github.com/zitadel/oidc/v3/pkg/oidc"
)
初期化処理
一連の処理にて構造体 rp.RelyingParty が必要となります。
ctx := context.Background()
provider, _ := rp.NewRelyingPartyOIDC(
ctx,
"issuer",
"client_id",
"client_secret",
"redirect_uri",
[]string{"openid"},
)
認可エンドポイント生成処理
// GET /auth へのアクセス
endpoint := rp.AuthURL("state", provider)
// 上記で生成したエンドポイントにリダイレクトされるようにクライアントに返す
トークン取得処理
// GET /callback へのアクセス
token, _ := rp.CodeExchange[*oidc.IDTokenClaims](ctx, "code", provider)
idToken, _ := token.Extra("id_token").(string) // coreos と同じ処理
IDトークン検証処理
if _, err := rp.VerifyIDToken[*oidc.IDTokenClaims](ctx, idToken, provider.IDTokenVerifier()); err != nil {
panic(error)
}
おわりに
どちらのライブラリを使っても省エネルギーで OIDC RP の実装ができるなと確認できました。
正直どちらも使用感は変わらなかったですが、zitadel のほうが rp.RelyingParty という構造体の管理で一連の処理が記述できる点は良いと感じました。
zitadel/oidc に以下のような記載があったりするので個人的にはこちらのライブラリを使っていこうと思います。
Easy to use OpenID Connect client and server library written for Go and certified by the OpenID Foundation
複数プロバイダーや複数のコールバックURIを管理するときの実装をいい感じにするためにはどうしようかなと迷っています …
今回実装したコードは以下に置いておきます。ご参考まで。