Amazon Cognito の ユーザープールに関する API を SDK を使わずにたたく

ふとしです。

例えば JavaScript では amazon-cognito-identity-js が使えることになっていますが、このブログを作る際に使った Nuxt3 beta では、import するとビルドできなくなってしまいました。

そこでビルドの仕組みをどうにかすることよりも、もっと汎用的に役立ちそうな、Cognito の API を SDK を使わずに叩く方法を調べていました。他の AWS のサービスや他の言語で似たようなことが発生した場合でも、リクエストを発行する方法さえ知っていればどうにかなりそうですしね。

(結局 Nuxt からは叩かずバックエンド経由で叩くことにしましたが SDK は使いませんでした)

エンドポイント

ホスト

以下に AWS 内サービスのそれぞれのエンドポイントのホストが網羅的に掲載されています。

東京の Cognito ユーザープールだと https://cognito-idp.ap-northeast-1.amazonaws.com ですね。AWS の API は処理内容の区別をエンドポイントやメソッドで行わずにリクエストの内容で行うので、何をするにせよアクセスする URL はこの一つだけです。

各命令

以下に網羅的に掲載されています。

今回行いたかった処理は期限切れを迎えたトークンのリフレッシュです。

どの命令を行いたいか指定する

ヘッダーに指定します。

Client::new()
    .post(env!("COGNITO_API_HOST"))
    .insert_header(("Content-Type", "application/x-amz-json-1.1"))
    .insert_header((
        "X-Amz-Target",
        "AWSCognitoIdentityProviderService.InitiateAuth",
    ))

Content-Type は固定で X-Amz-Target で命令を指定します。値は <targetPrefix>.<actionName> となっているようですが targetPrefix を調べるための公式の方法を見つけることができませんでした。

ワークアラウンドですが この質問 の回答を参考に aws-sdk-js/apis at master · aws/aws-sdk-js のファイルから検索できます。

InitiateAuth で検索して出てきた aws-sdk-js/cognito-idp-2016-04-18.min.json at 0d01fd96f6f11a91874cb00ded8b4960182c97d2 · aws/aws-sdk-js"targetPrefix": "AWSCognitoIdentityProviderService" とあるので、とりあえず使えるんじゃないでしょうか。(あやふや)

命令の内容を指定する

これはリクエストボディに含めます。

各命令のページ冒頭に書いてある Request Syntax が送信するパラメーターの概要です。

{
   "AnalyticsMetadata": { 
      "AnalyticsEndpointId": "string"
   },
   "AuthFlow": "string",
   "AuthParameters": { 
      "string" : "string" 
   },
   "ClientId": "string",
   "ClientMetadata": { 
      "string" : "string" 
   },
   "UserContextData": { 
      "EncodedData": "string"
   }
}

上記に続いて書いてある項目を一つずつ読み、必要な項目を埋めていきます。トークンのリフレッシュに必要なのは最低限以下です。

#[derive(Serialize)]
struct Params {
    AuthFlow: &'static str,
    AuthParameters: AuthParameters,
    ClientId: &'static str,
}

#[derive(Serialize)]
struct AuthParameters {
    #[serde(rename = "REFRESH_TOKEN")]
    refresh_token: String,
}

let p = Params {
    AuthFlow: "REFRESH_TOKEN_AUTH",
    AuthParameters: AuthParameters {
        refresh_token,
    },
    ClientId: env!("COGNITO_CLIENT_ID"),
};
let bytes = Client::new()
    .post(env!("COGNITO_API_HOST"))
    .insert_header(("Content-Type", "application/x-amz-json-1.1"))
    .insert_header((
        "X-Amz-Target",
        "AWSCognitoIdentityProviderService.InitiateAuth",
    ))
    .send_json(&p)
    .await?
    .body()
    .await?;

受け取る

あとはレスポンスを適当にデシリアライズして終わります。

#[derive(Deserialize)]
struct AuthenticationResult {
    AuthenticationResult: NewIdTokens,
}

#[derive(Deserialize)]
pub struct NewIdTokens {
    IdToken: String,
    AccessToken: String,
    TokenType: String,
    ExpiresIn: usize,
}

まとめ

X-Amz-Target が簡単にわかれば SDK の使い方をいちいち調べなくとも好き勝手できそうでしたが、ちょっとめんどくさいですね。

でもまあこれで便利ライブラリがなんらかで使えない事態になってもどうにかなりそうです。