Elastic Load Balancing
Application Load Balancer

Application Load Balancer を使用してユーザーを認証する

Application Load Balancer を設定して、ユーザーがアプリケーションにアクセスしたときに安全に認証できます。これにより、アプリケーションがビジネスロジックに集中できるように、ユーザーを認証する作業をロードバランサーに任せることができます。

次にユースケースがサポートされています。

  • OpenID Connect (OIDC) に準拠する ID プロバイダー (IdP) を使用してユーザーを認証します。

  • Amazon、Facebook、または Google などよく知られているソーシャル IdP を介して、Amazon Cognito でサポートされているユーザープールを経由して、ユーザーを認証します。

  • 企業アイデンティティを介して、SAML、LDAP、または Microsoft AD を使用して、Amazon Cognito でサポートされているユーザープールを経由して、ユーザーを認証します。

OIDC 準拠 IdP を使用する準備を整える

Application Load Balancer で OIDC 準拠 IdP を使用している場合は、以下を実行します。

  • IdP に新しい OIDC アプリを作成します。クライアント ID およびクライアントシークレットを設定する必要があります。

  • その IdP によって発行される次のエンドポイントを取得します。認証、トークン、およびユーザー情報。この情報は、よく知られている config で確認できます。

  • IdP アプリで次のリダイレクト URL のいずれかをホワイトリストに入れます。ユーザーが使用するものであればどれでも構いません。ここで DNS はロードバランサーのドメイン名であり、CNAME はアプリケーションの DNS エイリアスです。

    • https://DNS/oauth2/idpresponse

    • https://CNAME/oauth2/idpresponse

Amazon Cognito を使用する準備を整える

Application Load Balancer で Amazon Cognito ユーザープールを使用している場合は、以下を実行します。

  • ユーザープールを作成します。詳細については、Amazon Cognito 開発者ガイドの「Amazon Cognito ユーザープール」を参照してください。

  • ユーザープールのクライアントを作成します。クライアントシークレットを生成し、コード付与フローを使用し、ロードバランサーが使用するものと同じ OAuth スコープをサポートするように、クライアントを設定する必要があります。詳細については、Amazon Cognito 開発者ガイドの「ユーザープールアプリクライアントの設定」を参照してください。

  • ユーザープールのドメインを作成します。詳細については、Amazon Cognito 開発者ガイドの「ユーザープールのドメイン名の追加」を参照してください。

  • リクエストされたスコープで ID トークンが返されていることを確認します。たとえば、デフォルトのスコープ openid では ID トークンが返されますが、aws.cognito.signin.user.admin スコープでは返されません。

  • ソーシャルまたは企業 IdP と連携するには、フェデレーションセクションで IdP を有効にします。詳細については、Amazon Cognito 開発者ガイドの「ユーザープールにソーシャルサインインを追加する」または「SAML IdP を使用したサインインをユーザープールに追加する」を参照してください。

  • Amazon Cognito のコールバック URL フィールドで次のリダイレクト URL のいずれかをホワイトリストに入れます。ユーザーが使用するものであればどれでも構いません。ここで DNS はロードバランサーのドメイン名であり、CNAME はアプリケーションの DNS エイリアス (使用している場合) です。

    • https://DNS/oauth2/idpresponse

    • https://CNAME/oauth2/idpresponse

  • IdP アプリのコールバック URL でユーザープールドメインをホワイトリストに入れます。IdP の形式を使用します。以下に例を示します。

    • https://domain-prefix.auth.region.amazoncognito.com/saml2/idpresponse

    • https://user-pool-domain/oauth2/idpresponse

IAM ユーザーを有効にして、ロードバランサーで Amazon Cognito を使用してユーザーを認証するように設定するには、cognito-idp:DescribeUserPoolClient アクションを呼び出すアクセス許可をユーザーに付与する必要があります。

Amazon CloudFront を使用する準備を整える

Application Load Balancer の前面で CloudFront ディストリビューションを使用している場合は、次の設定を有効にします。

  • クエリ文字列の転送とキャッシュ (すべて)

  • Cookie の転送 (すべて)

  • リクエストヘッダーに基づいたキャッシュ (すべて)

ユーザー認証を設定する

1 つ以上のリスナールールの認証アクションを作成して、ユーザー認証を設定します。authenticate-cognito および authenticate-oidc アクションタイプは HTTPS リスナーでのみサポートされています。対応するフィールドの説明については、Elastic Load Balancing API リファレンスバージョン 2015-12-01の「AuthenticateCognitoActionConfig」および「AuthenticateOidcActionConfig」を参照してください。

デフォルトでは、SessionTimeout フィールドは 7 日に設定されています。セッションをこれより短くするばあいは、セッションタイムアウトを最短 1 秒に設定できます。詳細については、「認証のログアウトとセッションのタイムアウト」を参照してください。

アプリケーションの必要に応じて、OnUnauthenticatedRequest フィールドを設定します。以下に例を示します。

  • ユーザーがソーシャル ID または企業 ID を使用してログインする必要があるアプリケーション — デフォルトのオプション authenticate でサポートされています。ユーザーがログインしていない場合は、ロードバランサーはリクエストを IdP 認証エンドポイントにリダイレクトし、IdP によってユーザーにユーザーインターフェイスを使用したログインを求めるプロンプトが表示されます。

  • ログインしているユーザーにはパーソナライズされたビューを、ログインしていないユーザーには一般的なビューを提供するアプリケーション — このタイプのアプリケーションをサポートするには、allow オプションを使用します。ユーザーがログインしている場合、ロードバランサーがユーザークレームを提供するため、アプリケーションはパーソナライズされたビューを提供できます。ユーザーがログインしていない場合、ロードバランサーはリクエストをユーザークレームなしで転送するため、アプリケーションは一般的なビューを提供できます。

  • 数秒ごとに読み込みを行う JavaScript を使用した単一ページのアプリケーション — デフォルトでは、認証セッションの Cookie が有効期限切れになると、AJAX 呼び出しは IdP にリダイレクトされてブロックされます。deny オプションを使用する場合、ロードバランサーはこれらの AJAX 呼び出しに対して HTTP 401 Unauthorized エラーを返します。

ロードバランサーは、IdP トークンのエンドポイント (TokenEndpoint) および IdP ユーザー情報エンドポイント (UserInfoEndpoint) と通信できる必要があります。ロードバランサーのセキュリティグループおよび VPC のネットワーク ACL がこれらのエンドポイントに対するアウトバウンドアクセスを許可していることを検証します。VPC がインターネット接続されていることを確認します。内部向けロードバランサーがある場合は、NAT ゲートウェイを使用して、ロードバランサーからこれらのエンドポイントにアクセスできるようにします。

次の create-rule コマンドを使用して、ユーザー認証を設定します。

aws elbv2 create-rule --listener-arn listener-arn --priority 10 \ --conditions Field=path-pattern,Values="/login" --actions file://actions.json

authenticate-oidc アクションと forward アクションを指定する actions.json ファイルの例を次に示します。

[{ "Type": "authenticate-oidc", "AuthenticateOidcConfig": { "Issuer": "https://idp-issuer.com", "AuthorizationEndpoint": "https://authorization-endpoint.com", "TokenEndpoint": "https://token-endpoint.com", "UserInfoEndpoint": "https://user-info-endpoint.com", "ClientId": "abcdefghijklmnopqrstuvwxyz123456789", "ClientSecret": "123456789012345678901234567890", "SessionCookieName": "my-cookie", "SessionTimeout": 3600, "Scope": "email", "AuthenticationRequestExtraParams": { "display": "page", "prompt": "login" }, "OnUnauthenticatedRequest": "deny" }, "Order": 1 }, { "Type": "forward", "TargetGroupArn": "arn:aws:elasticloadbalancing:region-code:account-id:targetgroup/target-group-name/target-group-id", "Order": 2 }]

authenticate-cognito アクションと forward アクションを指定する actions.json ファイルの例を次に示します。

[{ "Type": "authenticate-cognito", "AuthenticateCognitoConfig": { "UserPoolArn": "arn:aws:cognito-idp:region-code:account-id:userpool/user-pool-id", "UserPoolClientId": "abcdefghijklmnopqrstuvwxyz123456789", "UserPoolDomain": "userPoolDomain1", "SessionCookieName": "my-cookie", "SessionTimeout": 3600, "Scope": "email", "AuthenticationRequestExtraParams": { "display": "page", "prompt": "login" }, "OnUnauthenticatedRequest": "deny" }, "Order": 1 }, { "Type": "forward", "TargetGroupArn": "arn:aws:elasticloadbalancing:region-code:account-id:targetgroup/target-group-name/target-group-id", "Order": 2 }]

詳細については、「リスナールール」を参照してください。

認証のフロー

Elastic Load Balancing では、OIDC 認証コードフローを使用します。これには、以下のステップが含まれます。

  1. 認証アクションを持つルールの条件が満たされている場合、ロードバランサーはリクエストヘッダーに認証セッション Cookie があるかどうかを確認します。Cookie が存在しない場合、ロードバランサーはユーザーを IdP 認証エンドポイントにリダイレクトし、IdP でユーザーを認証できるようにします。

  2. ユーザーが認証されると、IdP は認証付与コードを伴ったユーザーをロードバランサーにリダイレクトして戻します。ロードバランサーはコードを IdP トークンエンドポイントに示し、ID トークンやアクセストークンを取得します。

  3. ロードバランサーは ID トークンを検証した後、アクセストークンを IdP ユーザー情報エンドポイントと交換してユーザークレームを取得します。

  4. ロードバランサーは認証セッション Cookie を作成してクライアントに送信します。これにより、クライアントのユーザーエージェントがリクエストを送信したときにロードバランサーに Cookie を送信できます。ほとんどのブラウザでは Cookie のサイズを 4K に制限しているため、ロードバランサーはサイズが 4K を超える Cookie を複数の Cookie に分割します。IdP から受け取ったユーザークレームとアクセストークンの合計サイズが 11K を超える場合、ロードバランサーはエラーを返します。

  5. ロードバランサーは HTTP ヘッダーのターゲットにユーザークレームを送信します。詳細については、「ユーザークレームのエンコードと署名の検証」を参照してください。

  6. IdP によって ID トークンに有効な更新トークンが提供されている場合、ロードバランサーは更新トークンを保存し、アクセストークンの有効期限が切れるたびにこれを使用して、セッションがタイムアウトするか、IdP の更新に失敗するまで、ユーザークレームを更新します。ユーザーがログアウトすると、更新は失敗し、ロードバランサーはユーザーを IdP 認証エンドポイントにリダイレクトします。これにより、ロードバランサーはユーザーがログアウト後にセッションを削除できます。詳細については、「認証のログアウトとセッションのタイムアウト」を参照してください。

ユーザークレームのエンコードと署名の検証

ロードバランサーがユーザーの認証に成功すると、IdP から受け取ったユーザークレームがターゲットに送信されます。ロードバランサーはユーザークレームに署名するため、アプリケーションは署名の検証およびそのクレームがロードバランサーから送信されたものであることの検証を行うことができます。

ロードバランサーは以下の HTTP ヘッダーを追加します。

x-amzn-oidc-accesstoken

トークンエンドポイントからのアクセストークン (プレーンテキスト)。

x-amzn-oidc-identity

ユーザー情報エンドポイントからの件名フィールド (sub) (プレーンテキスト)。

x-amzn-oidc-data

ユーザークレーム (JSON ウェブトークン (JWT) 形式)。

完全なユーザークレームが必要なアプリケーションでは、任意の標準 JWT ライブラリを使用できます。JWT 形式にはヘッダー、ペイロード、および Base64 URL でエンコードされた署名が含まれています。JWT 署名は ECDSA + P-256 + SHA256 です。

この JWT ヘッダーは、次のフィールドを持つ JSON オブジェクトです。

{ "alg": "algorithm", "kid": "12345678-1234-1234-1234-123456789012", "signer": "arn:aws:elasticloadbalancing:region-code:account-id:loadbalancer/app/load-balancer-name/load-balancer-id", "iss": "url", "client": "client-id", "exp": "expiration" }

この JWT ペイロードは、IdP ユーザー情報エンドポイントから受け取ったユーザークレームを含む JSON オブジェクトです。

{ "sub": "1234567890", "name": "name", "email": "alias@example.com", ... }

ロードバランサーではユーザークレームが暗号化されないため、HTTPS を使用するようにターゲットグループを設定することをお勧めします。HTTP を使用するようにターゲットグループを設定する場合は、ロードバランサーへのトラフィックをセキュリティグループを使用するトラフィックのみに制限してください。また、クレームに基づいて認証を行う前に、署名を検証することもお勧めします。パブリックキーを取得するには、JWT ヘッダーからキー ID を取得し、それを使用して次のリージョンエンドポイントからパブリックキーを検索します。

https://public-keys.auth.elb.region.amazonaws.com/key-id

AWS GovCloud (US-West) の場合、エンドポイントは次のとおりです。

https://s3-us-gov-west-1.amazonaws.com/aws-elb-public-keys-prod-us-gov-west-1/key-id

次の例では、Python でパブリックキーを取得する方法を示しています。

import jwt import requests import base64 import json # Step 1: Get the key id from JWT headers (the kid field) encoded_jwt = headers.dict['x-amzn-oidc-data'] jwt_headers = encoded_jwt.split('.')[0] decoded_jwt_headers = base64.b64decode(jwt_headers) decoded_json = json.loads(decoded_jwt_headers) kid = decoded_json['kid'] # Step 2: Get the public key from regional endpoint url = 'https://public-keys.auth.elb.' + region + '.amazonaws.com/' + kid req = requests.get(url) pub_key = req.text # Step 3: Get the payload payload = jwt.decode(encoded_jwt, pub_key, algorithms=['ES256'])

認証のログアウトとセッションのタイムアウト

アプリケーションで認証済みユーザーをログアウトさせる必要がある場合、認証セッション Cookie の有効期限を -1 に設定し、クライアントを IdP ログアウトエンドポイントにリダイレクト (IdP でサポートされている場合) する必要があります。ユーザーが削除された Cookie を再利用しないようにするには、アクセストークンの有効期限を問題のない範囲でできるだけ短く設定することをお勧めします。有効期限切れのアクセストークンと null 以外の更新トークンがある認証セッション Cookie を持つロードバランサーをクライアントが提供する場合、ロードバランサーは IdP に接続してユーザーがまだログインしているかどうかを判別します。

更新トークンとセッションタイムアウトは連携して次のように動作します。

  • セッションタイムアウトがアクセストークンの有効期限よりも短い場合、ロードバランサーはセッションのタイムアウトを優先し、認証セッションがタイムアウトした後はユーザーに再度ログインさせます。

  • セッションタイムアウトがアクセストークンの有効期限よりも長く、IdP が更新トークンをサポートしていない場合、ロードバランサーは認証セッションをタイムアウトまで維持してから、ユーザーに再度ログインさせます。

  • セッションタイムアウトがアクセストークンの有効期限よりも長く、IdP が更新トークンをサポートしている場合、ロードバランサーはアクセストークンの有効期限が切れるたびにユーザーセッションを更新します。ロードバランサーは、認証セッションがタイムアウトした後、または更新フローに失敗した場合にのみ、ユーザーに再度ログインさせます。