AWS Cognitoのベストプラクティスを見つける
![nus3](https://res.cloudinary.com/zenn/image/fetch/s---K0FxmDX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/377a196371.jpeg)
今回、うまくまとめられてない
ぐちゃぐちゃ
- 環境構築
- cdk
- db
- バックエンド
- フロントエンド
- バックエンド
![nus3](https://res.cloudinary.com/zenn/image/fetch/s---K0FxmDX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/377a196371.jpeg)
環境構築(CDK)
cognitoをCDKで作る
npm i -g aws-cdk
cdk init app --language=typescript
- cdkかく
cdk deploy --require-approval never --profile {profile name}
-
cdk destroy --profile {profile name}
(リソース削除したい場合)
![nus3](https://res.cloudinary.com/zenn/image/fetch/s---K0FxmDX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/377a196371.jpeg)
cdkかく
必要最低限の設定
import * as cdk from "@aws-cdk/core";
import * as cognito from "@aws-cdk/aws-cognito";
type UserPoolConfig = {
id: string;
name: string;
};
type Params = {
description: string;
userPool: UserPoolConfig;
};
export class CognitoStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const params: Params = {
description:
"ハダのテスト用です,
userPool: {
id: "TestHada",
name: "test-hada",
},
};
this.createUserPool(params);
}
private createUserPool(params: Params): cognito.CfnUserPool {
return new cognito.CfnUserPool(this, params.userPool.id, {
userPoolName: params.userPool.name,
// TODO: 必要なコンフィグを設定する
});
}
}
![nus3](https://res.cloudinary.com/zenn/image/fetch/s---K0FxmDX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/377a196371.jpeg)
TODO: ユーザープール作るときの設定値を一個つづ調べる
![nus3](https://res.cloudinary.com/zenn/image/fetch/s---K0FxmDX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/377a196371.jpeg)
認証フロー
awsのドキュメント
![nus3](https://res.cloudinary.com/zenn/image/fetch/s---K0FxmDX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/377a196371.jpeg)
クライアント側での認証フロー
AWS SDK for JavaScriptを使ってクライアント側で認証する
sdkはSRPをサポートしててクライアント側でSRPの詳細を生成してくれる
クライアント側で実装してて辛かったこと
トランザクションを貼りづらかった
クライアントでサインアップ→バックエンドでユーザー情報をDB登録
でバックエンド側でDB登録の時にエラーが出た時にCognitoにユーザーはいるけどDBにはいない状態になる(リクエストでもらったユーザー情報からcognitoのユーザーも削除すれば良きだった?)
![nus3](https://res.cloudinary.com/zenn/image/fetch/s---K0FxmDX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/377a196371.jpeg)
サーバー側での認証フロー
ADMIN_USER_PASSWORD_AUTH
使うから(ADMIN_NO_SRP_AUTH)、srpが使われてないのでクライアントサイドがあるのであれば素直にクライアント側での認証フロー使うほうがいい?
bcryptとか使ってリクエスト送る前にパスワードをハッシュ化すれば良き?
bcryptはクライアントでやるんじゃなくてサーバー側でやる処理っぽい
クライアントからパスワードを受け取ってサーバー側でハッシュ化し、DBに保存
次回のログインからクライアントで送られてくるパスワードをサーバー側でDBに保存されたハッシュ値をbcryptライブラリを使って比較する
SSL通信であれば基本的には暗号化されてるからパスワードを平文で送っても大丈夫?
![nus3](https://res.cloudinary.com/zenn/image/fetch/s---K0FxmDX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/377a196371.jpeg)
用語
![nus3](https://res.cloudinary.com/zenn/image/fetch/s---K0FxmDX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/377a196371.jpeg)
未整理
カテゴリ分けがわからないものの一時置き場
![nus3](https://res.cloudinary.com/zenn/image/fetch/s---K0FxmDX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/377a196371.jpeg)
jwtの検証方法
![nus3](https://res.cloudinary.com/zenn/image/fetch/s---K0FxmDX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/377a196371.jpeg)
ECS使ってる場合はタスクロールでsdkで使うawsのリソースのポリシーを付与してあげればsession定義すればsdkを使える
![nus3](https://res.cloudinary.com/zenn/image/fetch/s---K0FxmDX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/377a196371.jpeg)
golangとreactのauth0のサンプルコード
![nus3](https://res.cloudinary.com/zenn/image/fetch/s---K0FxmDX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/377a196371.jpeg)
auth0のクイックスタートやってみた感じ、localstorageに保存せずともログイン状態は保持してくれるっぽい
![nus3](https://res.cloudinary.com/zenn/image/fetch/s---K0FxmDX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/377a196371.jpeg)
amplify(aws sdk for javascript)使ったら問答無用でlocal storageにjwtが保存される
![nus3](https://res.cloudinary.com/zenn/image/fetch/s---K0FxmDX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/377a196371.jpeg)
3/8時点でのまとめ
- cognitoでの認証フロー
- クライアント側
- サーバー側
- サーバー側での認証の場合、サーバーとcognito間はポリシーによって信頼されてる通信になるので
ADMIN_NO_SRP_AUTH
になる?- SPAだけどサーバー側での認証をするとクライアント・サーバー間はパスワードを平文で送ることになる(SSLしてたら大丈夫?)
- クライアント側はAWS SDK for JavaScriptがよしなにSRPしてくれる
- AWS SDK for JavaScript(amplify)はローカルストレージにjwtを保存する
- Auth0を使うとローカルストレージにjwtを保存せずともログイン状態が保持できる(サンプルコードでは)
- Auth0もcognitoもバックエンド側でのjwtの検証は同じような実装になる
- 基本的にローカルストレージはセキュアではない
- importしたライブラリに悪意のあるコードがあった場合、jwtは漏洩する可能性がある
TODO: jwtはローカルストレージに保存してもいいのかどうか決断する
TODO: auth0のローカルストレージに保存せずともログイン状態が保持できてる仕組みを調べる
![nus3](https://res.cloudinary.com/zenn/image/fetch/s---K0FxmDX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/377a196371.jpeg)
auth0のuniversal loginはhtmlをカスタマイズできる
universal login使う場合はログイン画面をreact側では定義できなそう
![nus3](https://res.cloudinary.com/zenn/image/fetch/s---K0FxmDX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/377a196371.jpeg)
Authorization Code with PKCE Flow
![nus3](https://res.cloudinary.com/zenn/image/fetch/s---K0FxmDX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/377a196371.jpeg)
auth0でuserのセッション情報を保持してる仕組みってここら辺かな
デフォルト、web worker使ってる?
Use Auth0 SPA SDK whose default storage option is in-memory storage leveraging Web Workers.
https://auth0.com/docs/tokens/token-storage#browser-in-memory-scenarios
browserのインメモリだとやっぱリロードした時に永続性が担保されないと
The in-memory method for browser storage does not provide persistence across page refreshes and browser tabs.
![nus3](https://res.cloudinary.com/zenn/image/fetch/s---K0FxmDX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/377a196371.jpeg)
local storageにtokenを保存する
- ブラウザの別タブ間やリロードでも永続性が担保される
- XSSを使用してSPA内でjsを実行できる場合、攻撃者はlocal storageにあるtokenを取得できる
- SPAのソースコード内か、SPAに含まれるサードパーティ性のライブラリからXSSにつながる脆弱性が出てくる可能性がある
https://auth0.com/docs/tokens/token-storage#browser-local-storage-scenarios
対策として
- tokenの期限を短くする(全てが解決されるわけではない)
- サードパーティ性のjsコードの使う量を減らす
![nus3](https://res.cloudinary.com/zenn/image/fetch/s---K0FxmDX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/377a196371.jpeg)
ginでのセッション管理
axiosの設定(フロントエンド側)
![nus3](https://res.cloudinary.com/zenn/image/fetch/s---K0FxmDX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/377a196371.jpeg)
jwtをsessionで保存する場合、csrfに気を付ける
session idがわかってしまえばそのsession idでリクエストを送ることができてしまうので
リクエストを送る際にcsrf tokenをバックエンド側で発行し、フロントエンド側で取得、csrf tokenをリクエストのヘッダに付与する
同期通信はSame Origin Policyの制約を受けない
csrfの例はこれが一番わかりやすかった
別ドメインであろうがformタグのactionで元のドメインを指定してsubmitすると元のドメインのcookieがheaderに付与される
![nus3](https://res.cloudinary.com/zenn/image/fetch/s---K0FxmDX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/377a196371.jpeg)
reactでのxss
基本、ユーザーが入力できる値でjavascript:
スキームやcreateElement
、dangerouslySetInnerHTML
を気にすれば良き?
![nus3](https://res.cloudinary.com/zenn/image/fetch/s---K0FxmDX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/377a196371.jpeg)
reactでのcsrf tokenのイメージ
useEffectでcsrf tokenをgetして、axiosのヘッダに埋め込む感じ
![nus3](https://res.cloudinary.com/zenn/image/fetch/s---K0FxmDX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/377a196371.jpeg)
golang側はいったん簡単に実装したいからこれ使うけどproductionでは使わないようにする