Cognitoのサインイン時に取得できる、IDトークン・アクセストークン・更新トークンを理解する

先日、Cognitoを使ってみるブログを書きまして、Cognitoを利用してサインインするとIDトークンアクセストークン更新トークン(リフレッシュトークン)が発行されることを説明しました。

プログラミングせずにCognitoで新規ユーザー登録&サインインを試してみる

本ブログでは、このIDトークンアクセストークン更新トークン(リフレッシュトークン)についてもう少し深堀りしていきます。

各トークンの主な用途

  • IDトークン:連携サービスの認証(例:API Gateway)と認証されたユーザー情報の参照
  • アクセストークン:Cognitoユーザープールのユーザー属性更新
  • 更新トークン:新しいIDトークン、アクセストークンの取得

細かい仕様は公式ドキュメントに記載されているので必要に応じて参照してください。

IDトークン

IDトークンは主に認証(例:API Gateway)に使用します。

API GatewayではCognitoをオーサライザーに設定している場合、ヘッダにCognitoで発行した期限切れでないIDトークンを指定することでAPIが呼び出せます。

また、認証されたユーザーのIDに関するクレーム(情報)が含まれているので、それをアプリケーションで利用できます。

IDトークンはJWT(JSON Web Token)です。ナンノコッチャと思うかもしれませんが、JWTはトークンを作るための標準規格だと思ってください。

JWTはAWSの用語ではなく一般的な話ですので、Wikipediaが参考になります。

JWTは次のような形式で構成されています。

JSON ウェブトークン (JWT) は、次の 3 つのセクションで構成されます。
1. ヘッダー
2. Payload
3. 署名

11111111111.22222222222.33333333333

Base64url 文字列でエンコードされ、ドット「.」文字で区切られています。

説明のとおりになっているのか確かめるため、実際にIDトークンを取得して、中身を見てみます。

$ USER_POOL_ID=us-west-2_xxxxxxxxx
$ CLIENT_ID=xxxxxxxxxxxxxxxxxxxxxxxxxx
$ USER_EMAIL="email@example.com"
$ PASSWORD="Password01@"
$ ID_TOKEN=$(aws cognito-idp admin-initiate-auth \
  --user-pool-id ${USER_POOL_ID} \
  --client-id ${CLIENT_ID} \
  --auth-flow ADMIN_NO_SRP_AUTH \
  --auth-parameters "USERNAME=${USER_EMAIL},PASSWORD=${PASSWORD}" \
  --query "AuthenticationResult.IdToken" | sed "s/\"//g") && echo ${ID_TOKEN}

そうすると、たしかに3つの文字列がドットで連結されているものだとわかります。

よって、ドットで文字列分割してBASE64デコードしてみると、認証されたユーザーのクレーム(情報)を表示できることがわかります。

$ echo ${ID_TOKEN} | cut -d'.' -f 1 | base64 -D
{
"kid" : "1234example="
"alg" : "RS256",
}
$ echo ${ID_TOKEN} | cut -d'.' -f 2 | base64 -D
{
"sub": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"aud": "xxxxxxxxxxxxexample",
"email_verified": true,
"token_use": "id",
"auth_time": 1500009400,
"iss": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_example",
"cognito:username": "janedoe",
"exp": 1500013000,
"given_name": "Jane",
"iat": 1500009400,
"email": "janedoe@example.com"
}

このようにして、IDトークン認証されたユーザーに関するクレーム(情報)を参照できます。

アクセストークン

アクセストークンは主にユーザープールのユーザー属性の追加・変更・削除に使用します。

アクセストークンもJWT(JSON Web Token)です。

試しに、アクセストークンを使ってユーザー属性を取得してみます。
AWS CLIでは aws cognito-idp get-user コマンドで実現できます。

まずはアクセストークンを取得します。

$ USER_POOL_ID=us-west-2_xxxxxxxxx
$ CLIENT_ID=xxxxxxxxxxxxxxxxxxxxxxxxxx
$ USER_EMAIL="email@example.com"
$ PASSWORD="Password01@"
$ ACCESS_TOKEN=$(aws cognito-idp admin-initiate-auth \
  --user-pool-id ${USER_POOL_ID} \
  --client-id ${CLIENT_ID} \
  --auth-flow ADMIN_NO_SRP_AUTH \
  --auth-parameters "USERNAME=${USER_EMAIL},PASSWORD=${PASSWORD}" \
  --query "AuthenticationResult.AccessToken" | sed "s/\"//g") && echo ${ACCESS_TOKEN}

続けて、ユーザー情報を取得します。

$ aws cognito-idp get-user \
  --access-token ${ACCESS_TOKEN}
{
    "Username": "email@example.com",
    "UserAttributes": [
        {
            "Name": "sub",
            "Value": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
        },
        {
            "Name": "email_verified",
            "Value": "true"
        },
        {
            "Name": "email",
            "Value": "email@example.com"
        }
    ]
}

ユーザー属性の更新もやってみます。

ユーザー属性の更新のためには、まずCognitoユーザープールに新しい属性を追加する必要があります。

マネジメントコンソールを利用して、カスタム属性を追加します。

変更の保存をして属性を確認すると、接頭辞に custom: が追記されて、カスタム属性が設定されていることがわかります。

次に、アプリクライアントからユーザーの属性を更新可能にします。

先ほど追加した属性にチェックを入れて、アプリクライアントの変更を保存します。

Cognitoユーザープールに新しい属性を追加し更新権限も付与したので、ユーザー属性の更新をやってみます。
AWS CLIでは aws cognito-idp update-user-attributes コマンドで実現できます。

$ aws cognito-idp update-user-attributes \
  --user-attributes Name=custom:my-attribute,Value=hogepiyo \
  --access-token ${ACCESS_TOKEN}

マネジメントコンソールからユーザー情報を見てみると、たしかに属性が更新されていることが確認できます。

このようにして、アクセストークンはCognitoユーザープールのユーザー属性追加・変更・削除に利用できます。

更新トークン

更新トークンは主に新しいIDトークンおよびアクセストークンの取得に使用します。

IDトークンとアクセストークンの有効期限1時間です。

1時間経つたびに再ログインさせるシステムだと使いにくいですね。そこで、更新トークンの出番です。

更新トークンをサインイン時に保存しておけば、更新トークンの有効期限が切れるまで、有効なIDトークンとアクセストークンを取得し続けることができます。

更新トークンの有効期限は、アプリクライアントの設定で1〜3650(日)の任意の値を指定できます。

試しに、更新トークンを使って、新しいIDトークンとアクセストークンを取得してみます。
AWS CLIではユーザーIDとパスワードのサインインと同じ aws cognito-idp admin-initiate-auth コマンドで実現できます。

まずは更新トークンを取得します。

$ USER_POOL_ID=us-west-2_xxxxxxxxx
$ CLIENT_ID=xxxxxxxxxxxxxxxxxxxxxxxxxx
$ USER_EMAIL="email@example.com"
$ PASSWORD="Password01@"
$ REFRESH_TOKEN=$(aws cognito-idp admin-initiate-auth \
  --user-pool-id ${USER_POOL_ID} \
  --client-id ${CLIENT_ID} \
  --auth-flow ADMIN_NO_SRP_AUTH \
  --auth-parameters "USERNAME=${USER_EMAIL},PASSWORD=${PASSWORD}" \
  --query "AuthenticationResult.RefreshToken" | sed "s/\"//g") && echo ${REFRESH_TOKEN}

続けて、更新トークンを利用して新しいIDトークンとアクセストークンを取得します。

$ aws cognito-idp admin-initiate-auth \
  --user-pool-id ${USER_POOL_ID} \
  --client-id ${CLIENT_ID} \
  --auth-flow REFRESH_TOKEN_AUTH \
  --auth-parameters "REFRESH_TOKEN=${REFRESH_TOKEN}"

そうすると、こんな形で新しいIDトークンとアクセストークンが取得できます。通常サインインしたときとは異なり、更新トークンはありません。

このようにして、更新トークン新しいIDトークンとアクセストークンの取得に利用できます。

(おまけ)ユーザーのトークンをすべて無効にする

発行したトークンは、サインアウトすることですべて無効にできます。

2019/10/07追記
サインアウトで無効になるのは、アクセストークンと更新トークンだけです。IDトークンは無効になりません。
参考:javascript - Is it possible to revoke AWS Cognito IdToken? - Stack Overflow

Cognitoでは有効なトークンを利用して認証を行っています。ですので、Cognitoにおけるサインアウトとはトークンを無効にすることなのです。

更新トークンが無効になっているので新しいIDトークンとアクセストークンを発行することができません。
サインアウトした後は、再度サインインして新しいトークンを発行するという作業が必要になるのです。

AWS CLIでは aws cognito-idp global-sign-out コマンドで実現できます。

$ aws cognito-idp global-sign-out \
  --access-token=${ACCESS_TOKEN}

サインアウトするとすべてのトークンが無効になり、先ほどやってみた新しいIDトークン等の取得も当然できなくなります。

$ aws cognito-idp admin-initiate-auth \
  --user-pool-id ${USER_POOL_ID} \
  --client-id ${CLIENT_ID} \
  --auth-flow REFRESH_TOKEN_AUTH \
  --auth-parameters "REFRESH_TOKEN=${REFRESH_TOKEN}"

An error occurred (NotAuthorizedException) when calling the AdminInitiateAuth operation: Refresh Token has been revoked

終わりに

Cognitoの認証は、これらトークンの発行と管理によって行われています。

このトークンに対する操作や管理を自分でやるのはしんどいので、AWS Amplify等のライブラリがいい感じにやってくれます。

ライブラリを利用すれば細かいトークンの仕様まで知らなくてもCognitoを利用することはできます。

けれども、Cognitoがいったい何をやっているのか中身を見てCognitoのお気持ちになってみるのもおもしろいものです。