Open28

AWS Cognitoのベストプラクティスを見つける

nus3nus3

今回、うまくまとめられてない
ぐちゃぐちゃ

  • 環境構築
    • cdk
    • db
    • バックエンド
  • フロントエンド
  • バックエンド
nus3nus3

環境構築(CDK)

cognitoをCDKで作る

  1. npm i -g aws-cdk
  2. cdk init app --language=typescript
  3. cdkかく
  4. cdk deploy --require-approval never --profile {profile name}
  5. cdk destroy --profile {profile name} (リソース削除したい場合)
nus3nus3

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: 必要なコンフィグを設定する
    });
  }
}
nus3nus3

TODO: ユーザープール作るときの設定値を一個つづ調べる

nus3nus3
nus3nus3

クライアント側での認証フロー

AWS SDK for JavaScriptを使ってクライアント側で認証する
sdkはSRPをサポートしててクライアント側でSRPの詳細を生成してくれる

クライアント側で実装してて辛かったこと

トランザクションを貼りづらかった
クライアントでサインアップ→バックエンドでユーザー情報をDB登録
でバックエンド側でDB登録の時にエラーが出た時にCognitoにユーザーはいるけどDBにはいない状態になる(リクエストでもらったユーザー情報からcognitoのユーザーも削除すれば良きだった?)

nus3nus3

サーバー側での認証フロー

https://note.kiriukun.com/entry/20191023-server-side-aws-cognito-user-pool-authentication-in-nodejs

ADMIN_USER_PASSWORD_AUTH使うから(ADMIN_NO_SRP_AUTH)、srpが使われてないのでクライアントサイドがあるのであれば素直にクライアント側での認証フロー使うほうがいい?

bcryptとか使ってリクエスト送る前にパスワードをハッシュ化すれば良き?
bcryptはクライアントでやるんじゃなくてサーバー側でやる処理っぽい
クライアントからパスワードを受け取ってサーバー側でハッシュ化し、DBに保存
次回のログインからクライアントで送られてくるパスワードをサーバー側でDBに保存されたハッシュ値をbcryptライブラリを使って比較する
https://github.com/kelektiv/node.bcrypt.js#readme

SSL通信であれば基本的には暗号化されてるからパスワードを平文で送っても大丈夫?

nus3nus3

用語

nus3nus3

SRP

Secure Remote Password

多分、以下の記事見る限りこんな感じ?

  • クライアント側はusernameとパスワードを平文で入力
  • パスワードをサーバー側に保存しない
  • クライアントでハッシュ値を生成(なんかアルゴリズム使って)
  • サーバー側でもハッシュ値を生成(なんかアルゴリズム使って)
  • 認証時にネットワークにパスワードの平文は流さず生成したハッシュ値を使う
  • サーバー側はハッシュ値を受け取ってユーザーが正しいかどうかを認証する

https://tociyuki.hatenablog.jp/entry/20110122/1295664897

nus3nus3

BCrypt

パスワードをセキュアに保存するのを支援するライブラリ、ハッシュ化するアルゴリズム?

nus3nus3

未整理

カテゴリ分けがわからないものの一時置き場

nus3nus3

ECS使ってる場合はタスクロールでsdkで使うawsのリソースのポリシーを付与してあげればsession定義すればsdkを使える

nus3nus3

amplify(aws sdk for javascript)使ったら問答無用でlocal storageにjwtが保存される

nus3nus3

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のローカルストレージに保存せずともログイン状態が保持できてる仕組みを調べる

nus3nus3

auth0のuniversal loginはhtmlをカスタマイズできる
universal login使う場合はログイン画面をreact側では定義できなそう

nus3nus3

auth0でuserのセッション情報を保持してる仕組みってここら辺かな
https://auth0.com/docs/tokens/refresh-tokens/refresh-token-rotation#maintain-user-sessions-in-spas

デフォルト、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.

nus3nus3

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コードの使う量を減らす
nus3nus3

jwtをsessionで保存する場合、csrfに気を付ける
session idがわかってしまえばそのsession idでリクエストを送ることができてしまうので
リクエストを送る際にcsrf tokenをバックエンド側で発行し、フロントエンド側で取得、csrf tokenをリクエストのヘッダに付与する

同期通信はSame Origin Policyの制約を受けない
https://qiita.com/mpyw/items/0595f07736cfa5b1f50c#html-フォーム送信のパターン
https://qiita.com/okamoai/items/044c03680766f0609d41#2-ブラウザの標準動作同期通信によるリクエスト

csrfの例はこれが一番わかりやすかった
別ドメインであろうがformタグのactionで元のドメインを指定してsubmitすると元のドメインのcookieがheaderに付与される
https://blog.jxck.io/entries/2018-10-26/same-site-cookie.html