mod_auth_openidcによりApache HTTP ServerをOpenID Connect Relying Partyにする -- Google編

こんにちは、滝澤です。

Apache HTTP ServerをOpenID Connect Relying Partyにするmod_auth_openidcというモジュールを使ってみる機会がありましたので、本記事で情報共有します。

なお、記事が長くなったので本編と設定例である3編に分けました。

本記事では、OP(OpenID Provider)としてGoogle(G Suite)を利用する場合の設定例を紹介します。

設定例

ウェブサイトURI

まず、ウェブサイトURIとリダイレクトURIを決めます。

ウェブサイトURIには、利用するウェブサイトのURIを指定します。このとき、パス名を付けない形式にしてください。

リダイレクトURIには、リダイレクトURI(redirect_uri)として使用されるURIを指定します。コンテンツが存在するURIに被らないものにしてください。このリダイレクトURI経由でリクエストのやりとりが行われます。

ウェブサイトURIhttps://example.com
リダイレクトURIhttps://example.com/redirect_uri

アプリケーションの登録とクライアントIDとクライアントシークレットの取得

G Suiteを使う場合は次のヘルプのページに従って、アプリケーションの登録とクライアントIDとクライアントシークレットを取得してください。

このとき、以下のような値を指定します。

アプリケーションの種類ウェブアプリケーション
承認済みの JavaScript 生成元先の「ウェブサイトURI」で指定した値(例: https://example.com)
承認済みのリダイレクト URI先の「リダイレクトURI」で指定した値(例: https://example.com/redirect_uri)

設定ファイルの編集

設定ファイル /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を記述します。 Googleの場合は次のようになります。

OIDCProviderMetadataURL https://accounts.google.com/.well-known/openid-configuration

ちなみに、執筆時点でのGoogleのメタデータの内容は次のようになります。 エンドポイントの情報だけでなく、サポートしているタイプやアルゴリズムなども取得できます。

$ curl https://accounts.google.com/.well-known/openid-configuration 2>/dev/null | jq
{
  "issuer": "https://accounts.google.com",
  "authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth",
  "token_endpoint": "https://www.googleapis.com/oauth2/v4/token",
  "userinfo_endpoint": "https://www.googleapis.com/oauth2/v3/userinfo",
  "revocation_endpoint": "https://accounts.google.com/o/oauth2/revoke",
  "jwks_uri": "https://www.googleapis.com/oauth2/v3/certs",
  "response_types_supported": [
    "code",
    "token",
    "id_token",
    "code token",
    "code id_token",
    "token id_token",
    "code token id_token",
    "none"
  ],
  "subject_types_supported": [
    "public"
  ],
  "id_token_signing_alg_values_supported": [
    "RS256"
  ],
  "scopes_supported": [
    "openid",
    "email",
    "profile"
  ],
  "token_endpoint_auth_methods_supported": [
    "client_secret_post",
    "client_secret_basic"
  ],
  "claims_supported": [
    "aud",
    "email",
    "email_verified",
    "exp",
    "family_name",
    "given_name",
    "iat",
    "iss",
    "locale",
    "name",
    "picture",
    "sub"
  ],
  "code_challenge_methods_supported": [
    "plain",
    "S256"
  ]
}

OIDCScope

scopeを指定します。 "openid"は必須です。

Googleの場合は、Authentication URI parametersより、"openid", "email", "profile"をサポートしていることがわかります。 これらを全て指定すると次のようになります。

OIDCScope "openid email profile"

OIDCAuthRequestParams

Authentication Requestの際に追加で要求するパラメータを指定します。

Googleの場合は、 Authentication URI parametersを見ると、prompt, display, hdなどのパラメータを追加で要求することができます。

promptパラメータには再認証および同意を要求するかを指定します。次の値を指定することができます。

  • none(認証や同意の画面を表示しない)
  • consent(クライアントにレスポンスを返す前に同意画面を表示する)
  • select_account(ユーザーアカウントの選択画面を表示する)

何も指定しなければ、同意画面やログイン画面なしに再認証されます。

displayパラメータには認証および同意のためのユーザーインタフェースを指定します。

  • page(ページ画面)
  • popup(ポップアップ)
  • touch(タッチインターフェース向け)
  • wap(フィーチャーフォン向け)

しかし、Googleの場合はこのパラメータを指定しても動作は変わらないため、指定する必要はありません。

hdパラメータにはG Suiteで利用しているドメイン名を指定します。指定しない場合には、Googleの認証に成功したユーザーすべてに対して、認可されてしまうことになります。 別途、apacheのアクセス制御としても認可を制御できますが、この段階で制御した方が好ましいでしょう。

ここでは、次のように設定して、認可される対象のドメイン名を"example.com"に制限します。

OIDCAuthRequestParams hd=example.com

同意画面を表示させたい場合は次のようにします。

OIDCAuthRequestParams prompt=consent&hd=example.com

OIDCResponseType

レスポンスタイプ(response_type)を指定します。

OpenID Connect Core 1.0 -- 3. Authenticationに次の内容の"response_type"とFlowの対応が記述されています。

"response_type" valueFlow
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

「Authorization Code Flow」の場合は次のように"code"を指定します。

OIDCResponseType "code"

OIDCClientID

クライアントID(client_id)を指定します。

OIDCClientID XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.apps.googleusercontent.com

OIDCClientSecret

クライアントシークレット(client_secret)を指定します。

OIDCClientSecret XXXXXXXXXXXXXXXXXXXXXXXX

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://"を除いたものになります。

Googleの場合は「sub@iss」の形式では「ユーザーを識別する約20桁の数字@accounts.google.com」となり、誰がログインしたのかわかりにくいので、一目で識別できる他の情報を指定してもよいでしょう。 この例では、emailから@の左側の文字列を取り出してユーザーIDとします。

OIDCRemoteUserClaim 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はユーザーのフルネームに、OIDC_CLAIM_pictureはユーザーのプロフィール画像のURLに置き換わります。

明示的にログアウトできるように、ログアウトするパス「/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>
<img src="<!--#echo var="OIDC_CLAIM_picture" -->" alt="" height="24" width="24"/>
<!--#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
        # G Suiteの認可設定:
        #   claimのhdがexample.comである場合のみ認可する。
        Require claim hd:example.com

        # 動作確認のための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による認証・認可の処理が行われます。

Google (G Suite)で認証済みでなければ、Googleのログインページにリダイレクトされるのでログインの操作を行います。

認証済みのユーザーの場合、あるいはログインが完了すると次のようなページが表示されます。

サイト画面

氏名とユーザーIDが表示されていれば成功です。

参考サイト

株式会社ハートビーツのインフラエンジニアから、ちょっとした情報をお届けします。