こんにちは、滝澤です。
Apache HTTP ServerをOpenID Connect Relying Partyにするmod_auth_openidcというモジュールを使ってみる機会がありましたので、本記事で情報共有します。
なお、記事が長くなったので本編と設定例である3編に分けました。
本記事では、OP(OpenID Provider)としてAzure Active Directory (以降、Azure ADと略す)を利用する場合の設定例を紹介します。
設定例
ウェブサイトURI
まず、ウェブサイトURIとリダイレクトURIを決めます。
ウェブサイトURIには、利用するウェブサイトのURIを指定します。このとき、パス名を付けない形式にしてください。
リダイレクトURIには、リダイレクトURI(redirect_uri)として使用されるURIを指定します。コンテンツが存在するURIに被らないものにしてください。このリダイレクトURI経由でリクエストのやりとりが行われます。
ウェブサイトURI | https://example.com |
リダイレクトURI | https://example.com/redirect_uri |
アプリケーションの登録とクライアントIDとクライアントシークレットの取得
Azure ADを使う場合は次のヘルプのページに従って、アプリケーションの登録を行います。
- AD テナントへのアプリケーションの登録 (Microsoft)
このとき、以下のような値を指定します。
アプリケーションの種類 | Web アプリ/API |
サインオン URL | 先の「ウェブサイトURI」で指定した値(例: https://example.com) |
応答 URL | 先の「リダイレクトURI」で指定した値(例: https://example.com/redirect_uri) |
ここで、「プロパティ」のブレードで表示される「アプリケーション ID」がクライアントIDになります。
さらに、「APIアクセス」の「キー」をクリックすると、「キー」のブレードが表示されるので、ここで「説明」と「有効期限」を入力して保存すると値が表示されます。これがクライアントシークレットになります。
設定ファイルの編集
設定ファイル /etc/httpd/conf.d/auth_openidc.conf を編集して、各ディレクティブを設定します。
以降、ディレクティブ毎に説明します。ただし、デフォルト値で特に問題ないと思われるものについては説明はしません。
OIDCRedirectURI
リダイレクトURI(request_uri)として指定した値を記述します。
OIDCRedirectURI https://example.com/redirect_uri
OIDCCryptoPassphrase
cookieやキャッシュエントリーなどの暗号化に利用するパスフレーズの文字列を指定します。
OIDCCryptoPassphrase XXXXXXXXXXXXXXXXXXXXXXXX
OIDCProviderMetadataURL
OpenID Connect ProviderメタデータのURIを記述します。 Azure ADの場合は次のようになります。
OIDCProviderMetadataURL https://login.microsoftonline.com/{tenant}/.well-known/openid-configuration
ここで、{tenant}をAzure ADのテナント名に置き換えます。
ちなみに、執筆時点でのAzure ADのメタデータの内容は次のようになります。 エンドポイントの情報だけでなく、サポートしているタイプやアルゴリズムなども取得できます。
$ curl https://login.microsoftonline.com/common/.well-known/openid-configuration 2>/dev/null | jq { "authorization_endpoint": "https://login.microsoftonline.com/common/oauth2/authorize", "token_endpoint": "https://login.microsoftonline.com/common/oauth2/token", "token_endpoint_auth_methods_supported": [ "client_secret_post", "private_key_jwt" ], "jwks_uri": "https://login.microsoftonline.com/common/discovery/keys", "response_modes_supported": [ "query", "fragment", "form_post" ], "subject_types_supported": [ "pairwise" ], "id_token_signing_alg_values_supported": [ "RS256" ], "http_logout_supported": true, "frontchannel_logout_supported": true, "end_session_endpoint": "https://login.microsoftonline.com/common/oauth2/logout", "response_types_supported": [ "code", "id_token", "code id_token", "token id_token", "token" ], "scopes_supported": [ "openid" ], "issuer": "https://sts.windows.net/{tenantid}/", "claims_supported": [ "sub", "iss", "cloud_instance_name", "cloud_graph_host_name", "aud", "exp", "iat", "auth_time", "acr", "amr", "nonce", "email", "given_name", "family_name", "nickname" ], "microsoft_multi_refresh_token": true, "check_session_iframe": "https://login.microsoftonline.com/common/oauth2/checksession", "userinfo_endpoint": "https://login.microsoftonline.com/common/openid/userinfo", "tenant_region_scope": null, "cloud_instance_name": "microsoftonline.com", "cloud_graph_host_name": "graph.windows.net" }
OIDCScope
scopeを指定します。
Azure ADでは"openid"しかサポートされていません。
$ curl https://login.microsoftonline.com/common/.well-known/openid-configuration 2>/dev/null | jq .scopes_supported [ "openid" ]
そのため、次のように設定します。
OIDCScope "openid"
参考までに、Azure AD v2.0では"openid", "profile", "email", "offline_access"がサポートされていると思われます。実際に利用できるかは未確認です。
$ curl https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration 2>/dev/null | jq .scopes_supported [ "openid", "profile", "email", "offline_access" ]
OIDCAuthRequestParams
Authentication Requestの際に追加で要求するパラメータを指定します。
サインイン要求を送信する(Microsoft)を見ると、promptを追加で要求することができます。
promptパラメータには再認証および同意を要求するかを指定します。次の値を指定することができます。
- none(認証や同意の画面を表示しない)
- login(再認証を求める認証画面を表示する)
- consent(クライアントにレスポンスを返す前に同意画面を表示する)
何も指定しなければ、同意画面やログイン画面なしに再認証されます。
promptパラメータを制御しない場合は、OIDCAuthRequestParamsを指定する必要はありません。
同意画面を表示させたい場合は次のようにします。
OIDCAuthRequestParams prompt=consent
OIDCResponseType
レスポンスタイプ(response_type)を指定します。
OpenID Connect Core 1.0 -- 3. Authenticationに次の内容の"response_type"とFlowの対応が記述されています。
"response_type" value | Flow |
---|---|
code | Authorization Code Flow |
id_token | Implicit Flow |
id_token token | Implicit Flow |
code id_token | Hybrid Flow |
code token | Hybrid Flow |
code id_token token | Hybrid Flow |
サインイン要求を送信する(Microsoft)には次のような記述があります。
OpenID Connect サインインでは、 id_token を指定する必要があります。 code などの他の response_types が含まれていてもかまいません。
しかし、"id_token"のみの指定では「Implicit Flow」になり、(RPをOPに対するクライアントとする)クライアント認証が行われません。
今回のケースでは、ウェブアプリケーションであるため、「Authorization Code Flow」を利用したいです。
次のように調べてみると、"code"をサポートしているので、「Authorization Code Flow」が使えそうです。
$ curl https://login.microsoftonline.com/common/.well-known/openid-configuration 2>/dev/null | jq .response_types_supported [ "code", "id_token", "code id_token", "token id_token", "token" ]
実際に試してみると、普通に使えるので、"code"を指定するのがよいでしょう。
「Authorization Code Flow」の場合は次のように"code"を指定します。
OIDCResponseType "code"
私見ですが、先のヘルプの記述はscopeと勘違いされているような気がします(scopeは"id_token"が必須)。
OIDCClientID
クライアントID(client_id)を指定します。
OIDCClientID XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
OIDCClientSecret
クライアントシークレット(client_secret)を指定します。
OIDCClientSecret XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
OIDCPKCEMethod
PKCE code challenge method(code_challenge_method)を指定します。 "S256"でよいでしょう。
OIDCPKCEMethod S256
OIDCSessionInactivityTimeout
無操作タイムアウトの秒数を指定します。デフォルトは300秒です。
OIDCSessionInactivityTimeout 300
OIDCSessionMaxDuration
セッション有効期間を秒数で指定します。デフォルトは28800秒(8時間)です。 0を指定すると、IDトークンの期限と同じになります。
OIDCSessionMaxDuration 28800
OIDCCacheType
キャッシュのストレージのタイプを指定します。
本記事の動作確認ではRedisを使うので、"redis"を指定します。
OIDCCacheType redis
さらに、関連するディレクティブの設定も行ってください。
例えば、redisを指定した場合は下記のようにOIDCRedisCacheServerとOIDCRedisCachePasswordの指定も行う必要があります。
OIDCRedisCacheServer 127.0.0.1 #OIDCRedisCachePassword 8dTjsdkngM4Y2okg3XjYgWRX
OIDCHTMLErrorTemplate
エラーページのテンプレートファイルのパスを指定します。
OIDCHTMLErrorTemplate /etc/httpd/conf.d/auth_openidc.error.html
上記の設定を行った場合は、ファイル"/etc/httpd/conf.d/auth_openidc.error.html"に次のような内容の記述を行ってください。1つ目の%sにはエラーメッセージが、2つ目の%sにはエラーの説明が埋め込まれます。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"/> <title>OpenID Connect: Error</title> </head> <body> <h1>OpenID Connect: Error</h1> <p>Message: %s</p> <p>Description: %s</p> <p><a href="/">Home</a></p> </body> </html>
OIDCDefaultLoggedOutURL
ログアウト後に遷移するURIを指定します。
OIDCDefaultLoggedOutURL https://example.com/loggedout.html
上記のように設定した場合は、次のよう内容のファイルloggedout.htmlをドキュメントルートに配置します。 再びログインできるようにトップページへにリンクを用意します。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"/> <title>OpenID Connect: Logged out</title> </head> <body> <h1>OpenID Connect: Logged out</h1> <p><a href="/">Login</a></p> </body> </html>
OIDCClaimPrefix
claimを環境変数やHTTPヘッダーフィールドにする際に付与するプレフィックスを指定します。
OIDCClaimPrefix OIDC_CLAIM_
例えば、claimの一つ"name"は環境変数あるいはHTTPヘッダーフィールドの名前としては"OIDC_CLAIM_name"になります。
リバースプロキシーとして動かしている場合は、バックエンド側ウェブサーバー側でHTTPヘッダーを環境変数に変換する際の"-"と"_"の曖昧性を排除するために、次のように"_"を使わないようにした方がよいと思われます。
OIDCClaimPrefix OIDC-CLAIM-
OIDCRemoteUserClaim
apacheの認証されたユーザーIDを示す変数REMOTE_USERとして設定するclaimの名前を指定します。
デフォルトでは「sub@iss」の形式になります。 subはエンドユーザーの識別子で、issはIssuerから"https://"を除いたものになります。
Azure ADの場合は「sub@iss」の形式では「ユーザーを識別できる約40桁の文字列@sts.windows.net/テナントID/」となり、誰がログインしたのかわかりにくいので、一目で識別できる他の情報を指定してもよいでしょう。 この例では、unique_nameが実質的にメールアドレスになるため、これを利用して、unique_nameの@の左側の文字列を取り出してユーザーIDとします。
OIDCRemoteUserClaim unique_name ^(.*)@
なお、scopeとしてemailが利用できないため、claimとしてemailを取得できません。 そのため、ここでemailを利用することができません。
OIDCPassClaimsAs
claimをアプリケーション環境にどのようにして渡すかを指定します。
- none
- environment (環境変数)
- headers (HTTPヘッダー)
- both (両方) (default)
OIDCPassClaimsAs both
OIDCAuthNHeader
リバースプロキシーとして動かしている場合は、REMOTE_USERの情報をバックエンドに送信するHTTPヘッダーに追加するヘッダーフィールド名を指定します。
OIDCAuthNHeader X-Remote-User
動作確認用コンテンツの配置
動作確認として取得した変数を表示するために、次の内容のSSIを使ったページindex.shtmlをドキュメントルートに配置してみます。
REMOTE_USER
はユーザーIDに、OIDC_CLAIM_name
はユーザーのフルネームに置き換わります。
明示的にログアウトできるように、ログアウトするパス「/redirect_uri?session=logout」へのリンクを用意します。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"/> <title>OpenID Connect</title> </head> <body> <h1>OpenID Connect</h1> <p> <!--#echo var="OIDC_CLAIM_name" --> (REMOTE_USER=<!--#echo var="REMOTE_USER" -->) </p> <p><a href="/redirect_uri?session=logout">Logout</a></p> </body> </html>
apacheの設定
apacheの設定は以下のようになります。
<Location /> # OpenID Connectによる認証・認可を要求する。 AuthType openid-connect # Azure AD用の認可設定: # claimのissが下記の値(https://sts.windows.net/〜)の場合のみ認可する。 # issuerはテナント毎にユニークであるため、組織内のユーザーに制限できる。 Require claim iss:https://sts.windows.net/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/ # 動作確認のためのSSI用の設定を行う。 Options +Includes DirectoryIndex index.shtml index.html </Location> # ログアウト後に遷移するページでは認証されていないユーザーにもアクセスを許可する。 <Location /loggedout.html> Require all granted </Location>
設定の反映
設定が完了したら、設定を反映するために、apacheを再起動してみましょう。
$ sudo apachectl configtest $ sudo systemctl reload httpd.service
実際にアクセスしてみて何か問題があれば、エラーログに出力されるので確認しましょう。
$ sudo less /var/log/httpd/error_log
動作確認
ウェブブラウザで動作確認用のサイトにアクセスすると、OpenID Connectによる認証・認可の処理が行われます。
Office 365やAzureなどのMicrosoftのサービスで認証済みでなければ、Microsoftのログインページにリダイレクトされるのでログインの操作を行います。
認証済みのユーザーの場合、あるいはログインが完了すると次のようなページが表示されます。
氏名とユーザーIDが表示されていれば成功です。
参考サイト
- modauthopenidc (GitHub)
- OpenID Connect と Azure Active Directory を使用する Web アプリケーションへのアクセスの承認 (Microsoft)
- OAuth 2.0 と Azure Active Directory を使用した Web アプリケーションへのアクセスの承認 (Microsoft)
- v2.0 プロトコル - OAuth 2.0 と OpenID Connect (Microsoft)
- v2.0 エンドポイントの使用が適しているかどうかを判断するには (Microsoft)