【Flutter × AWS without Amplify】Cognitoでログイン〜認可情報を使ってAPIを叩くまで
記載内容
Flutter × Cognito を利用した認証・認可の設定まで(API 叩けるようになるまで)の解説
パッケージ:amazon_cognito_identity_dart_2 ※非公式
API に接続するには Cognito 側での認可情報が必要とする
※セッション情報の保持については扱いません。
⇒ アプリなら Shared Preferences、Web なら Cookies クラスでいけたと思います。
詳しくはまた別の記事にしようかと
※API Gateway の設定については扱いません。
(前段)なぜ Amplify を使わないのか?
1.制約が見えない
Firebase も同じことが言えますが、Amplify は AWS の各サービスを抽象化してパッケージングしたサービスです。パッケージングされた時に元のサービスの挙動や仕様がそのままであればいいのですが「実はこういう制約がありました」なんてことがあると厄介なので、手が出しづらいと思っています。
特に私自身が AWS に馴染みがあるわけではないので、そういうリスクはなるべく避けていきたいなという思いから、Amplify を使わないという判断に至りました。
2.自身の学習・スキルアップのため
Amplify で構築するとさぞかし楽なんだと思います。
実際 AWS ではないですが、Firebase × Flutter で個人開発をしていたときはすごく楽に感じました。インフラ周りの考慮事項が少ない(というかほぼ無い)ですし、特に Firestore に至ってはアプリクライアントと Firestore が直接接続するように CRUD 処理を行うので、通信経路すら考えなくても済みました。
しかし、そうやって考慮事項を後回しにしていると”ツケが回る”わけでして、知識のある方々のお話についていけなかったり、少し抽象度を下げてカスタマイズしようとすると途端に知識のハードルが上がってついていけなくなったりと大変で、到底実務レベルとは言えないと思いました。そんなことから、Amplify なしで構築していくことで、
AWS のサービスそのものや、AWS を使う前提として必要な知識について棚卸し・キャッチアップをしていこうと思いました。
ざっくり手順
1.前提となる知識を理解する("理解するまでの壁"を参照)
2.Amazon Cognito の公式ドキュメントを読む
3.実装
理解するまでの壁
HTTP プロトコルについての理解
- リクエスト、レスポンス
- GET,PUT,POST,DELETE
- リクエストライン、ヘッダ、ボディ
トークン認証についての理解
- クレデンシャル(Credentials)
- OAth 2.0
- OIDC
- アクセストークン
- リフレッシュトークン
- ID トークン
- JWT トークン
- ヘッダー
- ペイロード
- 署名
参考:
AWS の用語の理解
-
署名バージョン 4
-
(Cognito 直下の)ユーザープール
- ユーザープール名
- ユーザープール ID
- クライアント ID
- ユーザー名
- ユーザー ID(Sub)
- グループ
- グループ名
-
フェデレーティッドアイデンティティ
- ID プール
- ユーザープールグループ
- ID プールの ID
- アイデンティティ ID
余談
「Cognito で認可情報を取得できたユーザーのみ API を叩ける」を実現するためには、
フェデレーティッドアイデンティティの設定が必要です。
私は公式ドキュメントの読込が甘かったため、ここで躓きました。。。
実装の流れ
1.Cognito でユーザープールや、テスト用アカウントのセットアップ
※このときフェデレーティッドアイデンティティも設定してください 2.セットアップした内容(ユーザープール名やユーザープール ID といった固定値)を定義 3.パッケージで提供されている Cognito のオブジェクトに格納 4.セッションの確立 5.セッション情報からトークンとクレデンシャルの取得 6.署名バージョン4の設定
7.API を叩く
実装
const String email = 'ログイン用のメールアドレス';
const String password = 'ログイン用のパスワード';
// ユーザープール名
const String cognitoDomain = 'ユーザープール名';
// ユーザープールID
const String cognitoUserPoolId = 'ap-northeast-1_XXXXXXXXX';
// クライアントID
const String cognitoClientId = 'XXXXXXXXXXXXXXXXXXXXXXXXXX';
// IDプールのID
const String cognitoFedIdPoolId =
'ap-southeast-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
// ユーザープールオブジェクトの設定
final CognitoUserPool cognitoUserPool = CognitoUserPool(
cognitoUserPoolId,
cognitoClientId,
);
// クレデンシャル
final CognitoCredentials credentials = CognitoCredentials(
cognitoFedIdPoolId,
cognitoUserPool,
);
// 認証情報の設定
final CognitoUser cognitoUser =
CognitoUser(email, cognitoUserPool);
final AuthenticationDetails authDetails = AuthenticationDetails(
username: email,
password: password,
);
// セッション情報を格納するオブジェクトを定義
CognitoUserSession? session;
// ログイン処理、セッション確立
session = await cognitoUser.authenticateUser(authDetails);
// トークンの発行getJwtToken() ⇒ クレデンシャルの取得getAwsCredentials()
await credentials.getAwsCredentials(session!.getIdToken().getJwtToken());
// 署名バージョン4の設定
final AwsSigV4Client awsSigV4Client = AwsSigV4Client(
credentials.accessKeyId!,
credentials.secretAccessKey!,
// APIのエンドポイント
'https://xxxx.execute-api.ap-southeast-1.amazonaws.com/dev',
sessionToken: credentials.sessionToken,
region: 'リージョンが入ります',
);
// 署名バージョン4を使ったリクエスト用のオブジェクトを設定
final SigV4Request sigV4Request = SigV4Request(
awsSigV4Client,
method: 'GET',
path: '',
headers: Map<String, String>.from({'header-1': 'one', 'header-2': 'two'}),
queryParams: Map<String, String>.from({'tracking': 'x123'}),
body: Map<String, dynamic>.from({'color': 'blue'}),
);
// HTTPレスポンスを受け取るオブジェクトを定義
http.Response response;
// APIを叩く
try {
response = await http.get(
Uri.parse(sigV4Request.url!)
// headersにAuthorization項目を設定、JWTトークンを入れる
// パッケージのUse caseだと後述のbodyと同じようにsigV4Request.headers
// として書かれていますが、型のエラーが出たため変更しています
headers: Map<String, String>.from({
'Authorization': session!.getIdToken().getJwtToken(),
'header-2': 'two',
}),
// http.postなどでボディ部分を設定する必要がある場合
// body: sigV4Request.body,
);
} catch (e) {
print(e);
}
Discussion