LoginSignup
38
25

More than 3 years have passed since last update.

Flutterアプリのユーザー認証システムにAmazon Cognitoを使う(OAuth 2, Google, LINEなど)

Last updated at Posted at 2020-09-06

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ユーザーを作って作業をすることは忘れずに:bird:
ここでは

  • メールアドレスとパスワードによる認証
  • 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 LoginStart nowConsole home を開きます.ここでは自分たちが実際に利用したプロバイダーがありますが,最初は何もない状態だと思いますので新しく作成してください.

(2) プロバイダーを開いたらCreate a LINE Login Channelを選択します.
Channel Typeの選択ではLINE Loginを,以下のような入力画面が出てくるのでうちのApp typesではWeb App を選択.後の名前などは自分のプロダクトにあった名前を付けてください.
optional の項目は入力しなくて大丈夫です.


(3) createをした後表示される画面でChannel IDChannel 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/

このままプロバイダーを作成しようとすると,

検出により結果は返されませんでした。発行者を確認し、もう一度検出を実行するか、以下の必須のフィールドを手動で追加します。

と表記されるので,

を入力.
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種類のトークンについてはすぐ下に記載しています.

メールアドレス・パスワード認証の場合

  1. 登録・ログイン時に更新トークンを取得する.
  2. 更新トークンを利用して, トークンエンドポイントから, 残り2種類のトークンを取得する.
  3. 一意なユーザーIDを取得する(IDトークンをデコードする or IDプールのIDを取得する).
  4. 2で取得したIDトークンとアクセストークンを利用して, APIコールをする.

サードパーティ認証の場合

  1. 登録・ログイン時にAuthorization Codeを取得する. このコードは1回きりしか使用できない.
  2. Authorization Codeを利用して, トークンエンドポイントから, 3種類のトークンを取得する.
  3. 一意なユーザーIDを取得する(IDトークンをデコードする or IDプールのIDを取得する).
  4. 2で取得したIDトークンとアクセストークンを利用して, APIコールをする.

更新トークン・IDトークン・アクセストークン

Amazon Cognitoのトークンエンドポイントから取得できる3種類のトークンについては, 以下の記事などを参考にしてください.

イメージとしては,

  1. 更新トークンを取得
  2. その更新トークンを利用して, IDトークン・アクセストークンを取得
  3. IDトークンとアクセストークンを利用してCognitoの認証を行う

という感じだと思っています. 更新トークンはどこかに保管しておいて, APIコールを呼び出すタイミングでIDトークンやアクセストークンの有効期限が切れていれば, トークンエンドポイントからそれらを取得して使うという感じだと思います.

更新トークン IDトークン アクセストークン
設定可能な有効期限 60分~3650日 5分~1日 5分~1日
主な用途(?) IDトークン・アクセストークンを取得する APIコールなどに使用 APIコールなどに使用

使用するパッケージ

いつも通りpubspec.yamlに以下を追記してflutter pub getします(バージョンは最新のものを使用してください).

pubspec.yaml
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パッケージに渡す際に://が含まれないものを渡す必要があるため, このようにしています.
  • cognitoUserPoolamazon_cognito_identity_dart_2パッケージのCognitoUserPoolオブジェクトです. これに付随するメソッド(.getRegion(), .getClientId())を利用するために定義しています.

これ以降の説明ではこの変数を使っていきます. これらを使用するDartファイルに, このDartファイルをimportしてください. このファイルをauth.dartと呼ぶことにします.

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だとします.

  1. 上(auth.dart)で定義した, cognitoUserPoolに対して, signUp()メソッドで登録を行います. 戻り値は, CognitoUserPoolData型のオブジェクトであり, ここではcognitoUserPoolDataとします. メールアドレスに不備があったり, パスワードの要件を満たさなかったりした場合は, CognitoClientExceptionが発生します. エラーなく実行できた場合は, userEmailのメールアドレス宛にレジストレーションキーが送信されています.
  2. 上で返ってきた, cognitoUserPoolDatauserプロパティは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だとします.

  1. userEmailと, 上(auth.dart)で定義した, cognitoUserPoolからCognitoUser型のオブジェクトcognitoUserを作成します.
  2. userEmailuserPasswordからAuthenticationDetails型のオブジェクトauthenticationDetailsを作成します.
  3. cognitoUserに対して, authenticationDetailsを引数に渡したauthenticateUser()メソッドでユーザーを認証できます. パスワードが違うなどの例外もキャッチできます. 戻り値は, CognitoUserSession型のオブジェクトであり, ここではcognitoUserSessionとします.
  4. (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だとします.

  1. userEmailと, 上(auth.dart)で定義した, cognitoUserPoolからCognitoUser型のオブジェクトcognitoUserを作成します.
  2. cognitoUserに対して, forgotPassword()メソッドを実行し, userEmailのメールアドレス宛にリセットコードを送信します. ただ, このメソッドでは, userEmailがCognitoに存在するか(すでに登録されたものか)を判別していないため, 登録されていないメールアドレスを入力しても例外が発生しないことに注意してください.
  3. 2が問題なく実行できた場合, cognitoUserに対して, confirmPassword()メソッドを実行して新しいパスワードを登録します. 引数は, userResetCodeuserNewPasswordです. ここでもパスワードが要件を満たしていないなどの例外をキャッチできます.
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でログイン・新規登録)

サードパーティで認証を行う場合, ログイン・新規登録の流れは以下のようになります. ログインの場合でも新規登録の場合でも挙動は同じです.

  1. 認証エンドポイントにアクセス
  2. ユーザーがサードパーティのアカウントでログインすると, Authorization Codeが返ってくる.→アプリにリダイレクト
  3. 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に任せます.

使い方は簡単で, アクセスさせたいurlredirect_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の部分は設定したコールバックスキームに書き換えてください. ://は含みません.

android/app/src/main/AndroidManifest.xml
<!-- 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を受け取るところまでを書くと, 以下のようになります. responsehttp.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を受け取るところまでを書くと, 以下のようになります. responsehttp.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トークンが格納されているとします.
1. idTokenをamazon_cognito_identity_dart_2のCognitoIdTokenオブジェクトにします.
2. getJwtToken()メソッドでJwtTokenに変換します.
3. それをJwt.parseJwt()でデコードします.
4. payloadMapで, 今回はその中の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トークンが格納されているとします.
1. idTokenCognitoIdTokenオブジェクトにします.
2. CognitoCredentialsオブジェクトを作成します. ここでの引数は, 上(auth.dart)で定義したString型のcognitoIdentityPoolIdと, CognitoUserPoolオブジェクトのcognitoUserPoolです.
3. cognitoCredentials.getAwsCredentials()JwtTokenを渡して, AwsCredentialsを取得します.
4. cognitoCredentialsuserIdentityIdプロパティが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. オーソライザーを作成する

  1. 下のように, 左のメニューからオーソライザーを選び, 「新しいオーソライザーの作成」をクリックします.

  1. 名前は適当につけ, タイプは「Cognito」にし, 上で作成したCognitoユーザプールを選びます(リージョンを選んで右のフィールドをクリックすればユーザープール一覧が出てきます. 出てこない場合はリージョンを確認してください). トークンのソースも適当なものを入力してください. 終わったら, 「作成」をクリックしてください.

2. リソースに認可を与える

  1. 左のメニューから「リソース」を選び, 認可を与えたいリソースのメソッドを選択してください.

  2. 「メソッドリクエスト」をクリックしてください.

3.「認可 なし」となっているところの横のペンマークをクリックして, ドロップダウンから先ほど作成したオーソライザーを選択してください. 出てこない場合は, ページを再読み込みしてください. 選択できたら, 横のチェックマークをクリックしてください。

Flutter側からhttpリクエストを送る

idToken, accessTokenは上で取得したものです. また, apiEndpointauth.dartで定義した変数です.
APIによっても様々なので一例として

  • リソースパスが/testapiというAPI Gateway内のリソースに対して
  • methodPOST
  • headersrequestHeaders
  • bodyrequestBody

のリクエストを送る場合を書いています. 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_2CognitoUserクラスにはcacheTokens()メソッドや, clearCachedTokens()メソッドがあるので, これらを使うことができるかもしれません(やったことはないです).

38
25
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
38
25