Special Thanks! : @to-jiki と共同でこの記事を作成しました.
結構長いのでご注意!
Flutterを使ったアプリで, ユーザー認証のシステムを作りたいと思い, バックエンドをAWSで実装する必要があったため, Amazon Cognitoを使うことになりました. ただ, Flutter+Firebaseの情報は結構出てくるのですが, Flutter+Amazon Cognitoの方があまり出てこなくて困ったのでメモ程度にまとめたいと思います. 個人的に結構困った, Flutter部分も結構詳しめに書いています.
1つ注意していただきたいのが, FlutterもAWSも初心者の状態でいろいろ手探りなため, 正直遠回しなところや, よくない事をしているところもあるかもしれません. 参考程度にとどめて, あくまで自己責任でやっていただきたいと思います.
また, この記事ではバックエンドとの通信はAPI Gateway+Lambdaを用いたフルサーバーレスなアプリケーションの作成を想定しています(furaiev/amazon-cognito-identity-dart-2).
その他のアーキテクチャの場合は, furaiev/amazon-cognito-identity-dart-2のReadmeをご覧ください.
以下, リージョンはすべてus-east-1でやっていますが, そこは適宜読み替えてください.
Amazon Cognitoの設定
AWSのアカウントの作成等の作業はここでは省略します.IAMユーザーを作って作業をすることは忘れずに
ここでは
- メールアドレスとパスワードによる認証
- Googleアカウントによる認証
- LINEアカウントによる認証
のCognito及び関連(GCP,LINE Developer)の設定方法についてそれぞれ紹介します.
上記二つの設定の大半はmakotomiさんの記事を参考にさせていただきました.似たような内容になってしまったのですが細かい設定や,後に使用したプラグインなどで異なる部分はあるので参考にされる方々は両方目を通していただけると幸いです.
メールアドレスとパスワードによる認証
ユーザープールの作成
初めに認証したユーザーを保存しておくユーザープールを作成します.これはGoogle,LINEによる認証でも使用します.
(1) ユーザープール名を決め,デフォルトを確認するを選択します.
(2) 属性で設定したい認証方法に沿った項目を設定します.(今回は、Eメールアドレスおよび電話番号)
ここの設定はプール作成をしてしまうと変更することはできないので,よく確認してください.
また下の方にある標準属性でemailを選択するとうまく動かないことがあります.
(3) アプリクライアントの作成をしてアプリごとに紐付けを行うID(アプリクライアントID)を発行します.ユーザープール作成後に表示されます.
クライアントシークレットは外して,あとはお好みで設定してください.
(4) ポリシー等でパスワードの条件等を設定できるので欲しい要件にあった設定を行いプールの作成を押してください.
(5) 作成後,後で必要になるプールIDとアプリクライアントIDは控えておいてください.
メールアドレスとパスワードによる認証のCognito側の準備は以上です.
コードの中でどのように使うかは設定を全部した後で話そうと思います.
Googleアカウントによる認証
Googleとの認証の連携ではGoogle Cloud Platformを使用します.自分のGoogleアカウントを用いて登録してください.
(1) 初めにProjectの作成をします.
(2) APIとサービス➞OAuth同意画面でアプリケーションの作成を行います.
設定はデフォルトで大丈夫です.
(3) 認証情報➞+認証情報を作成➞OAuthクライアントIDを選択して,
ウェブアプリケーションの種類と名前を決め作成します.
~.apps.googleusercontent.comというクライアントIDとクライアントシークレットが作成されます.Cognitoとの連携で使用するのでメモしておいてください.
(4) 一旦Cognitoのほうに戻ります.
ダッシュボードの下のほうにあるIDプロバイダーを開きGoogleを選択します.
ここで先ほどメモしたIDを使用します.
- GoogleアプリID → クライアントID
- アプリシークレット → クライアントシークレット
- 承認スコープ → profile email openid
(5) アプリクライアントの設定の有効なプロバイダですべてを有効にして以下のように設定します.
コールバックURL(Flutter側の設定ではcallbackSchemeと呼んでいます)はログインの処理が終わった時の遷移先のことです. 今回の記事の中ではmyapp://
としています.
コールバックURLはCognito側とアプリ側で統一すればどんなものでも構いませんが, すこしややこしいので最後が
://
で終わるようにしてください. また, 本当はmyapp://
という簡易的なものではなく, ほかにスマホにインストールされているアプリのコールバックスキームと重複しない固有なものにするべきです. 今回はわかりやすさのためmyapp://
としていますが, 実際にアプリを作る際は, 一意なものになるようにしてください. 例えば,jp.foo.bar.ci78mls2://
などランダムな文字列を入れたりすると良いかもしれません.
(6) ドメイン名を選択しプールの場所を示してくれるAmazon Cognito ドメイン作成します.使用可能なドメインかチェックをして変更の保存をしてください.
再びGoogleの設定へ戻ります.
(7) GCPのAPIとサービス → 認証情報で先ほど作成したクライアントの承認済みURLに,6で作成したドメインに/oauth2/idpresponseを付加した
https://[自分で決めたドメイン].auth.us-east-1.amazoncognito.com/oauth2/idpresponse
を入力して保存します.
以上でGoogelアカウントによるログインでのCognito,GCPの設定は以上です.
LINEアカウントによる認証
LINEによる認証ではLINE Developer を使用します. 特別に何か登録する必要はないので自身のLINEアカウントでログインするだけで大丈夫です.
(1) 初めにプロバイダーを作成します.LINE Login → Start now → Console home を開きます.ここでは自分たちが実際に利用したプロバイダーがありますが,最初は何もない状態だと思いますので新しく作成してください.
(2) プロバイダーを開いたらCreate a LINE Login Channelを選択します.
Channel Typeの選択ではLINE Loginを,以下のような入力画面が出てくるのでうちのApp typesではWeb App を選択.後の名前などは自分のプロダクトにあった名前を付けてください.
optional の項目は入力しなくて大丈夫です.
(3) createをした後表示される画面でChannel IDとChannel secretの値はまたCoginito側の設定をするときに必要なのでメモしておいてください.
(4) Basic settings の隣にあるLINE Loginの中にあるCallback URLにGoogleの時と同様にhttps://[自分で決めたドメイン].auth.us-east-1.amazoncognito.com/oauth2/idpresponse
を入力します.
最後にDevelpoingをPublishedにしてLINE側の設定は終わりです.
Cognitoの設定に移動します.
(5) IDプロバイダーを開いて今度はLINE専用のプロバイダはないのでOpenID Connect選択します.クライアントIDとシークレットに4のLINEのChannelでメモしたIDとシークレットを入力.
承認スコープにはprofile mail openid
発行者には https://access.line.me/
このままプロバイダーを作成しようとすると,
検出により結果は返されませんでした。発行者を確認し、もう一度検出を実行するか、以下の必須のフィールドを手動で追加します。
と表記されるので,
- 認証エンドポイント → https://access.line.me/oauth2/v2.1/authorize
- トークンエンドポイント → https://api.line.me/oauth2/v2.1/token
- ユーザ情報エンドポイント → https://api.line.me/v2/profile
を入力.
Jwks uriは特に入力する必要はないのですが,https:// から始まるURLを入れないとエラーが出るので適当に入れておきましょう.
(6) 後はアプリクライアントの設定で有効なIDプロバイダで作成したLINEを選択して保存すれば設定は完了です.
おまけ
後のアプリとの連携でつかうのでIDプールも作成しておきます.
認証プロバイダーにそれぞれメモしたIDを入力して作成プールの作成を行います.
作成後の画面でIDプールのIDが表示されるのでこれもメモしておきましょう.
Flutterの設定
ユーザー認証付きのアプリを作成する場合, ユーザーが登録・ログインしたタイミングでユーザーごとに一意なIDを取得し, それを使ってAPIコールをしたりすると思います.
ここでは, 登録・ログインしてから, 一意なユーザーIDを取得するところまでを解説します. また, おまけとして, Cognitoで認証されたユーザーのみがアプリ内からAPIコールを呼び出す方法についても見てみます.
これ以下のコードでは例外はtry-catch
でキャッチして, print()
しているだけですので, 必要な処理は書き足すようにしてください. また, async-await
文もawait
の部分のみ書いているところも注意してください.
また, これ以下で書いていることを使って簡単なユーザー認証アプリのデモを作ったので, もし良ければご覧ください. コードを見ながら, 実際に使いながらのほうがわかりやすいかもしれません.
ただし
-
auth.dart
にはダミーの文字列が入れてあるので, 実際に動かすには上記のCognitoで設定した各値が必要 - ログインに成功したりしても, トークンを
print()
したりするだけの簡易的なものなので, 続きは必要に応じて書いていく必要あり
であることに注意してください.
ユーザー認証のおおまかな流れは以下のようになります. 3種類のトークンについてはすぐ下に記載しています.
メールアドレス・パスワード認証の場合
- 登録・ログイン時に更新トークンを取得する.
- 更新トークンを利用して, トークンエンドポイントから, 残り2種類のトークンを取得する.
- 一意なユーザーIDを取得する(IDトークンをデコードする or IDプールのIDを取得する).
- 2で取得したIDトークンとアクセストークンを利用して, APIコールをする.
サードパーティ認証の場合
- 登録・ログイン時にAuthorization Codeを取得する. このコードは1回きりしか使用できない.
- Authorization Codeを利用して, トークンエンドポイントから, 3種類のトークンを取得する.
- 一意なユーザーIDを取得する(IDトークンをデコードする or IDプールのIDを取得する).
- 2で取得したIDトークンとアクセストークンを利用して, APIコールをする.
更新トークン・IDトークン・アクセストークン
Amazon Cognitoのトークンエンドポイントから取得できる3種類のトークンについては, 以下の記事などを参考にしてください.
- Cognitoのサインイン時に取得できる、IDトークン・アクセストークン・更新トークンを理解する
- Refresh Token: どのような場合に使用し、どのように JWT と相互作用するか
- OAuth 2.0/OpenID Connectの2つのトークンの使いみち
イメージとしては,
- 更新トークンを取得
- その更新トークンを利用して, IDトークン・アクセストークンを取得
- IDトークンとアクセストークンを利用してCognitoの認証を行う
という感じだと思っています. 更新トークンはどこかに保管しておいて, APIコールを呼び出すタイミングでIDトークンやアクセストークンの有効期限が切れていれば, トークンエンドポイントからそれらを取得して使うという感じだと思います.
更新トークン | IDトークン | アクセストークン | |
---|---|---|---|
設定可能な有効期限 | 60分~3650日 | 5分~1日 | 5分~1日 |
主な用途(?) | IDトークン・アクセストークンを取得する | APIコールなどに使用 | APIコールなどに使用 |
使用するパッケージ
いつも通りpubspec.yaml
に以下を追記してflutter pub get
します(バージョンは最新のものを使用してください).
http: ^0.12.2
amazon_cognito_identity_dart_2: ^0.1.16
flutter_web_auth: ^0.2.4
jwt_decode: ^0.1.0
また, この記事だけでは情報が十分でないところは多々あると思いますので, 各パッケージのAPI Referenceも参照するようにしてください.
ユーザープール, IDプールの情報をまとめておく
毎回これらの情報を直接打ち込むのは運用・保守の観点からしてもよろしくないので, 何か1つのDartファイルにまとめて書いておきます. 自分たちの設定したものに書き換えてください.
-
callbackScheme
には://
を除いたものを入れていることに注意してください. FlutterWebAuthパッケージに渡す際に://
が含まれないものを渡す必要があるため, このようにしています. -
cognitoUserPool
はamazon_cognito_identity_dart_2パッケージのCognitoUserPool
オブジェクトです. これに付随するメソッド(.getRegion(), .getClientId()
)を利用するために定義しています.
これ以降の説明ではこの変数を使っていきます. これらを使用するDartファイルに, このDartファイルをimportしてください. このファイルをauth.dart
と呼ぶことにします.
import 'package:amazon_cognito_identity_dart_2/cognito.dart';
// Cognito User Pool
// 自分で決めたドメイン
// ex.) 'foo-bar123'
const cognitoDomain = 'foo-bar123';
// ユーザープールID
// ex.) 'us-east-1_XXXXXXXXX'
const cognitoUserPoolId = 'us-east-1_XXXXXXXXX';
// アプリクライアントID
// ex.) 'xxxxxxxxxxxxxxxxxxxxxxxxxx'
const cognitoClientId = 'xxxxxxxxxxxxxxxxxxxxxxxxxx';
// コールバックURL
// "://"が含まれないことに注意
const callbackScheme = 'myapp';
// 今回はAUthorization Codeでのレスポンスが欲しいので'code'を指定
// ほかにも'token'なども指定可能
const cognitoOAuthResponseType = 'code';
// ユーザープールで設定したScopeのうちここで使いたいScopeをスペースで区切って入力
const cognitoScope = 'openid email';
// Create Cognito User Pool
// 上で設定したプールIDとアプリクライアントIDをつかって, CognitoUserPoolクラスのオブジェクトを作成.
// これ以降, このオブジェクトを使いまくります
final cognitoUserPool = CognitoUserPool(cognitoUserPoolId, cognitoClientId);
// Cognito ID Pool
// IDプールのID
// ex.) 'us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
const cognitoIdentityPoolId = 'us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
// Lambda / API Gateway
// ex.) 'https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com'
const apiEndpoint = 'https://xxxxxxxxxx.execute-api.us-east-1.amazonaws.com';
メールアドレスとパスワードで認証
新規登録
ユーザーが入力したメールアドレス・パスワードがuserEmail
, userPassword
だとします. また, ユーザーが入力したレジストレーションキーがuserRegistrationKey
だとします.
- 上(
auth.dart
)で定義した,cognitoUserPool
に対して,signUp()
メソッドで登録を行います. 戻り値は,CognitoUserPoolData
型のオブジェクトであり, ここではcognitoUserPoolData
とします. メールアドレスに不備があったり, パスワードの要件を満たさなかったりした場合は,CognitoClientException
が発生します. エラーなく実行できた場合は,userEmail
のメールアドレス宛にレジストレーションキーが送信されています. - 上で返ってきた,
cognitoUserPoolData
のuser
プロパティはCognitoUser
型のオブジェクトです. これに対して,confirmRegistration()
メソッドで, レジストレーションキーの確認をすることができます. ここでも, 例外が発生した場合は,CognitoClientException
でキャッチできます.
import 'package:amazon_cognito_identity_dart_2/cognito.dart';
// Registration
try {
CognitoUserPoolData cognitoUserPoolData = await cognitoUserPool.signUp(userEmail, userPassword);
} on CognitoClientException catch (e) {
print(e);
}
// Confirm Registration Key
try {
await cognitoUserPoolData.user.confirmRegistration(userRegistrationKey);
} on CognitoClientException catch (e) {
print(e);
}
ログイン
ユーザーが入力したメールアドレス・パスワードがuserEmail
, userPassword
だとします.
-
userEmail
と, 上(auth.dart
)で定義した,cognitoUserPool
からCognitoUser
型のオブジェクトcognitoUser
を作成します. -
userEmail
とuserPassword
からAuthenticationDetails
型のオブジェクトauthenticationDetails
を作成します. -
cognitoUser
に対して,authenticationDetails
を引数に渡したauthenticateUser()
メソッドでユーザーを認証できます. パスワードが違うなどの例外もキャッチできます. 戻り値は,CognitoUserSession
型のオブジェクトであり, ここではcognitoUserSession
とします. - (optional)
cognitoUserSession
には各種トークンの情報が含まれるため, それらを取得して,userId
を取得するのに使えます(後述の 一意なユーザーIDを取得する を参照).
import 'package:amazon_cognito_identity_dart_2/cognito.dart';
CognitoUser cognitoUser = CognitoUser(userEmail, cognitoUserPool);
final authenticationDetails = AuthenticationDetails(username: userEmail, password: userPassword);
try {
final cognitoUserSession = await cognitoUser.authenticateUser(authenticationDetails);
// 以下の3つは必要に応じて使ってください.
final refreshToken = cognitoUserSession.getRefreshToken().token;
final idToken = cognitoUserSession.getIdToken().jwtToken;
final accessToken = cognitoUserSession.getAccessToken().jwtToken;
} catch (e) {
print(e);
}
パスワードを忘れた場合
ユーザーが入力したメールアドレス・パスワードがuserEmail
, userNewPassword
だとします. また, ユーザーが入力したリセットコードがuserResetCode
だとします.
-
userEmail
と, 上(auth.dart
)で定義した,cognitoUserPool
からCognitoUser
型のオブジェクトcognitoUser
を作成します. -
cognitoUser
に対して,forgotPassword()
メソッドを実行し,userEmail
のメールアドレス宛にリセットコードを送信します. ただ, このメソッドでは,userEmail
がCognitoに存在するか(すでに登録されたものか)を判別していないため, 登録されていないメールアドレスを入力しても例外が発生しないことに注意してください. - 2が問題なく実行できた場合,
cognitoUser
に対して,confirmPassword()
メソッドを実行して新しいパスワードを登録します. 引数は,userResetCode
とuserNewPassword
です. ここでもパスワードが要件を満たしていないなどの例外をキャッチできます.
import 'package:amazon_cognito_identity_dart_2/cognito.dart';
CognitoUser cognitoUser = CognitoUser(userEmail, cognitoUserPool);
try {
await cognitoUser.forgotPassword();
} catch (e) {
print(e);
}
// Create New Password
try {
await cognitoUser.confirmPassword(userResetCode, userNewPassword);
} catch (e) {
print(e);
}
パスワードの変更
ここでは詳しく紹介しませんが, 上と同様にcognitoUser
に対してchangePassword()
メソッドを実行すればパスワードの変更ができます.
CognitoUser
クラスにはほかにも様々なメソッドがあるので, 是非API Referenceを確認してみてください.
サードパーティで認証(Google, LINEでログイン・新規登録)
サードパーティで認証を行う場合, ログイン・新規登録の流れは以下のようになります. ログインの場合でも新規登録の場合でも挙動は同じです.
- 認証エンドポイントにアクセス
- ユーザーがサードパーティのアカウントでログインすると, Authorization Codeが返ってくる.→アプリにリダイレクト
- Authorization Codeを利用して, トークンエンドポイントから, 3種類のトークンを取得する.
1. 認証エンドポイント
認証エンドポイントは, 以下のようになり, Cognitoユーザープールでの設定により変わります.
アプリ内で「Googleでログイン」などのボタンをユーザーが押すと, このURLにアクセスさせ, 認証が終わればresponse_type
に従ったレスポンスが返ってきます.
-
{}
の部分は上(auth.dart
)で定義した変数に置き換えます. もちろんですが, すべて文字列として結合するので, ''は含まれません. -
cognitoRegion
は上(auth.dart
)で定義した変数ではありませんが, ここでは便宜上このように書いています.final cognitoRegion = 'us-east-1'
などに定義しておいても良いのですが, 先述のように,cognitoUserPool.getRegion()
と,cognitoUserPool
に対してgetRegion()
メソッドを使えばとってくることができるので, わざわざ定義していません. -
redirect_uri
はCognitoユーザープールで設定したものと正確に一致している必要があり,callbackScheme
に://
を書き足していることに注意してください.
https://{cognitoDomain}.auth.{cognitoRegion}.amazoncognito.com/login?response_type={cognitoOAuthResponseType}&client_id={cognitoClientId}&scope={cognitoScope}&redirect_uri={callbackScheme}://
このままでも使えるのですが, どのサードパーティアカウントで認証したいのかをもう一度ユーザーが選ばなければならないのが不満だったので, 以下のURLに直接アクセスさせるようにしました.
identityProvider
は, どのサードパーティアカウントで認証させるかにより変わります. 例えばGoogleであればGoogle
が入りますし, Open ID Connectの場合は, その設定の際に入力した「プロバイダー名」がidentityProvider
となります.
https://{cognitoDomain}.auth.{cognitoRegion}.amazoncognito.com/oauth2/authorize?response_type={cognitoOAuthResponseType}&client_id={cognitoClientId}&identity_provider={identityProvider}&scope={cognitoScope}&redirect_uri={callbackScheme}://
2. アプリにリダイレクト
上の認証エンドポイントのURLをインターナルブラウザで開いて, レスポンスを受け取るところまでを, flutter_web_authに任せます.
使い方は簡単で, アクセスさせたいurl
とredirect_uri(callbackScheme)
をFlutterWebAuth.authenticate()
に渡すだけです. 戻り値の型はもちろんFuture
型なのでasync-await
で使います.
ユーザーがアクセスしたにも関わらず, ログインせずに戻ってきた場合はPlatformException
を吐くので, エラーを検出するためにtry-catch
文で書くと例えば以下のようになります.
try {
final result = await FlutterWebAuth.authenticate(url: url, callbackUrlScheme: callbackScheme);
} on PlatformException catch (e) {
print(e);
}
ここでの, callbackScheme
は上で変数に格納したように, ://
を含まないことに注意してください.
ここで返ってくるresult
は以下のような形になります.
myapp://?code=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
これのxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
の部分がAuthorization Codeです.
これを以下のようにparseしてとってきます.
final cognitoAuthCode = Uri.parse(result).queryParameters['code'];
Androidの場合はAndroidManifest.xmlの編集が必要
Androidでアプリにリダイレクトさせるためには追加の設定が必要です.
android/app/src/main/AndroidManifest.xml
に以下を追記します. 上と同じですが, myapp
の部分は設定したコールバックスキームに書き換えてください. ://
は含みません.
<!-- Flutter Web Auth -->
<activity android:name="com.linusu.flutter_web_auth.CallbackActivity">
<intent-filter android:label="flutter_web_auth">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<!-- ここは書き換える -->
<data android:scheme="myapp"/>
</intent-filter>
</activity>
追記する場所ですが, <manifest>
→<application>
タグの中に書きます. デフォルトであれば, <application>
タグの中には, 最初に<activity android:name=".MainActivity" …
で始まる<activity>
タグがあると思いますが, その<activity>
タグと並列になるように追記します.
3. トークンエンドポイントからトークンを取得する
2で取得したAuthorization Codeを使ってトークンエンドポイントにhttpリクエストを送り(ここで, httpパッケージを使います), トークンを受け取ります. 詳細はトークンエンドポイント - Amazon Cognitoを参照してください.
トークンエンドポイントは, 以下のようになります.
https://{cognitoDomain}.auth.{cognitoRegion}.amazoncognito.com/oauth2/token
また, ヘッダーは固定で,
{'Content-Type': 'application/x-www-form-urlencoded'}
となっています.
あと必要な情報は, リクエストのbodyですが, ここでは,
- Authorization Codeから更新トークン・IDトークン・アクセストークンを取得する方法
- 更新トークンからIDトークン・アクセストークンを取得する方法
を解説します.
Authorization Codeから更新トークン・IDトークン・アクセストークンを取得する
bodyに必要な中身は以下のようになります.
-
'grant_type'
は'authorization_code'
で固定です. 下4つのパラメータの値は, 上(auth.dart
)で定義した変数を使っています. - こちらの
'redirect_uri'
もCognito ユーザープールで設定したものと正確に一致する必要があり,'myapp://'
のように://
が含まれるため, 下の表ではcallbackScheme
と'://'
を連結していることに注意してください.
パラメータ | 値 |
---|---|
'grant_type' |
'authorization_code' |
'client_id' |
cognitoUserPool.getClientId() (cognitoCliendId でも同じ) |
'code' |
cognitoAuthCode |
'redirect_uri' |
callbackScheme + '://' |
'scope' |
cognitoScope |
全体としてまとめて, httpリクエストを送ってresponseを受け取るところまでを書くと, 以下のようになります. response
はhttp.Response
型です.
import 'package:http/http.dart' as http;
// tokenEndPointのURLを作成
final tokenEndpoint = Uri.https(cognitoDomain + '.auth.' + cognitoUserPool.getRegion() + '.amazoncognito.com', '/oauth2/token');
http.Response response;
try {
response = await http.post(
tokenEndpoint,
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: {
'grant_type': 'authorization_code',
'client_id': cognitoUserPool.getClientId(),
'code': cognitoAuthCode,
'redirect_uri': callbackScheme + '://',
'scope': cognitoScope,
},
);
} catch (e) {
print(e);
}
このresponse
から, 各種トークンを取得するには以下のように, dart:convert
に含まれるjson.decode()
を利用して(importが必要です),
import 'dart:convert';
final refreshToken = json.decode(response.body)['refresh_token'];
final idToken = json.decode(response.body)['id_token'];
final accessToken = json.decode(response.body)['access_token'];
となります.
更新トークンからIDトークン・アクセストークンを取得する
bodyに必要な中身は以下のようになります.
-
'grant_type'
は'refresh_token'
で固定です.'client_id'
は上(auth.dart
)で定義した変数を使っています. -
'refresh_token'
は, これ以前にどこかで(例えば上のAuthorization Codeなどから)取得したものを指定します. ここでは,refreshToken
という変数に格納されているとします.
パラメータ | 値 |
---|---|
'grant_type' |
'refresh_token' |
'client_id' |
cognitoUserPool.getClientId() (cognitoCliendId でも同じ) |
'refresh_token' |
refreshToken |
全体としてまとめて, httpリクエストを送ってresponseを受け取るところまでを書くと, 以下のようになります. response
はhttp.Response
型です.
import 'package:http/http.dart' as http;
// tokenEndPointのURLを作成
final tokenEndpoint = Uri.https(cognitoDomain + '.auth.' + cognitoUserPool.getRegion() + '.amazoncognito.com', '/oauth2/token');
http.Response response;
try {
response = await http.post(
tokenEndpoint,
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: {
'grant_type': 'refresh_token',
'client_id': cognitoUserPool.getClientId(),
'refresh_token': refreshToken,
},
);
} catch (e) {
print(e);
}
このresponse
から, 各種トークンを取得するには以下のように, dart:convert
に含まれるjson.decode()
を利用して(importが必要です),
import 'dart:convert';
final idToken = json.decode(response.body)['id_token'];
final accessToken = json.decode(response.body)['access_token'];
となります.
一意なユーザーIDを取得する
ここでは, ユーザーを識別するために必要な一意なユーザーIDを取得する方法を解説します. 以下の2つの方法を見ていきます.
- 取得したIDトークンをデコードして, ユーザープールでいう
sub
を取得する - 取得した更新トークンを利用して, IDプールのユーザーIDを取得する
上記2つのIDは別物であるため, どちらを使うかはアプリ内で統一してください.
IDトークンをデコードする
取得したIDトークンは, 単純にエンコードされたものなので, デコードすることで中身を知ることができます. デコードに, jwt_decodeパッケージを使用します.
ここでは, idToken
に取得したIDトークンが格納されているとします.
-
idToken
をamazon_cognito_identity_dart_2のCognitoIdToken
オブジェクトにします. -
getJwtToken()
メソッドでJwtToken
に変換します. - それを
Jwt.parseJwt()
でデコードします. -
payload
はMap
で, 今回はその中のsub
を取得する.
import 'package:amazon_cognito_identity_dart_2/cognito.dart';
import 'package:jwt_decode/jwt_decode.dart';
final cognitoIdToken = CognitoIdToken(idToken);
final payload = Jwt.parseJwt(cognitoIdToken.getJwtToken());
final userId = payload['sub']
これで, 一意なユーザーID(userId
)を取得することができました.
ちなみに, payload
にはsub
だけでなく, 例えばemail
など, ユーザーのほかの情報も含まれるため, 同様にしてそれらとってくることもできます.
参考(再掲) : Cognitoのサインイン時に取得できる、IDトークン・アクセストークン・更新トークンを理解する
IDプールのユーザーIDを取得する
先述のsub
でも構いませんが, こちらのIDもユーザーごとに一意なのでユーザーを識別するためのIDとして利用できます
ここでは, idToken
に取得したIDトークンが格納されているとします.
-
idToken
をCognitoIdToken
オブジェクトにします. -
CognitoCredentials
オブジェクトを作成します. ここでの引数は, 上(auth.dart
)で定義したString
型のcognitoIdentityPoolId
と,CognitoUserPool
オブジェクトのcognitoUserPool
です. -
cognitoCredentials.getAwsCredentials()
にJwtToken
を渡して,AwsCredentials
を取得します. -
cognitoCredentials
のuserIdentityId
プロパティがIDプールのユーザーIDです.
import 'package:amazon_cognito_identity_dart_2/cognito.dart';
final cognitoIdToken = CognitoIdToken(idToken);
CognitoCredentials cognitoCredentials = CognitoCredentials(cognitoIdentityPoolId, cognitoUserPool);
await cognitoCredentials.getAwsCredentials(cognitoIdToken.getJwtToken());
final userId = cognitoCredentials.userIdentityId;
これで, 一意なユーザーID(userId
)を取得することができました.
(おまけ) APIコールについて
この記事はAPIに関することがメインではないので, ここでは実際にLambda関数を作ったりするようなことはしません. また, 前提としてLambdaとAPI Gatewayはつながっているとします.
AWS側の設定
AWS側で少し設定が必要です.
1. オーソライザーを作成する
- 下のように, 左のメニューからオーソライザーを選び, 「新しいオーソライザーの作成」をクリックします.
- 名前は適当につけ, タイプは「Cognito」にし, 上で作成したCognitoユーザプールを選びます(リージョンを選んで右のフィールドをクリックすればユーザープール一覧が出てきます. 出てこない場合はリージョンを確認してください). トークンのソースも適当なものを入力してください. 終わったら, 「作成」をクリックしてください.
2. リソースに認可を与える
-
左のメニューから「リソース」を選び, 認可を与えたいリソースのメソッドを選択してください.
-
「メソッドリクエスト」をクリックしてください.
3.「認可 なし」となっているところの横のペンマークをクリックして, ドロップダウンから先ほど作成したオーソライザーを選択してください. 出てこない場合は, ページを再読み込みしてください. 選択できたら, 横のチェックマークをクリックしてください。
Flutter側からhttpリクエストを送る
idToken
, accessToken
は上で取得したものです. また, apiEndpoint
はauth.dart
で定義した変数です.
APIによっても様々なので一例として
- リソースパスが
/testapi
というAPI Gateway内のリソースに対して -
method
がPOST
-
headers
がrequestHeaders
-
body
がrequestBody
のリクエストを送る場合を書いています. response
がもちろんレスポンスです. ちなみに, リソースパスは, 以下の部分で確認できます.
import 'package:amazon_cognito_identity_dart_2/cognito.dart';
CognitoUserSession cognitoUserSession = CognitoUserSession(idToken, accessToken);
final cognitoCredentials = CognitoCredentials(cognitoIdentityPoolId, cognitoUserPool);
await cognitoCredentials.getAwsCredentials(cognitoUserSession.getIdToken().getJwtToken());
final awsSigV4Client = AwsSigV4Client(cognitoCredentials.accessKeyId, cognitoCredentials.secretAccessKey, apiEndpoint, sessionToken: cognitoCredentials.sessionToken, region: cognitoUserPool.getRegion());
final signedRequest = SigV4Request(awsSigV4Client, method: 'POST', path: '/testapi', headers: requestHeaders, body: requestBody);
http.Response response;
try {
response = await http.post(
signedRequest.url,
headers: signedRequest.headers,
body: signedRequest.body,
);
} catch (e) {
print(e);
}
ログイン状態の保持について
ここまで見てきたように, 例えばユーザーのパスワード変更や, APIコールなどにはIDトークン・アクセストークンが必要でした. しかし, 「更新トークン・IDトークン・アクセストークン」のところで見たように, IDトークン・アクセストークンの有効期限は長くても1日です. つまり, このままでは, ユーザーは1日ごとにログインしなくてはならなく, とても不便です.
そこで, 有効期限が長く設定できる更新トークンに着目します. 「更新トークンからIDトークン・アクセストークンを取得する」で見たように, 更新トークンさえあればIDなど必要なものをすべて取得できます.
ただし, もちろん更新トークンにも有効期限がありますから, その有効期限が切れた際はログアウト状態となり, 再度ログインが必要となります. つまり, この方法を取れば, ユーザーがログイン操作をする必要のある頻度は, 更新トークンの有効期限の長さに反比例するようにできます.
実装方法ですが, 例えば, shared_preferencesというパッケージを使い, 更新トークンをSharedPreferencesに保存するという方法があります.
他には, amazon_cognito_identity_dart_2のCognitoUser
クラスにはcacheTokens()
メソッドや, clearCachedTokens()
メソッドがあるので, これらを使うことができるかもしれません(やったことはないです).