LoginSignup
5
8

More than 3 years have passed since last update.

Cognito UserPoolsのAuthorizerをGoでデコードする

Last updated at Posted at 2020-05-24

概要

  • AWSのAPI Gatewayの認証にCognito UserPoolsのAuthorizerを用いるときに、API側でもユーザーの一意性を保つために認証します
  • その際Cognitoから取得できるJWTのデコードが必要になるのでGo言語でのデコード方法についてまとめました
  • クライアント側でAmplifyやリダイレクト後のURLなどを用いてCognitoからIDトークンを取得する方法については既知とします

Amazon Cognito ユーザープール認証

  • Cognitoを用いたログインをクライアントから行うと、ユーザープールからJWT(JSON Web Token)が返されます
  • JWTはBase64でエンコードされたJSON文字列であり、ユーザーに関する情報(クレーム)が含まれています
    • (このBase64でエンコードされていると言う情報によりかなり苦しむことになりました...)
  • エンコードされた文字列は***.***.***のようにドット(.)で3つのセクションに分けられています
  • 3つのセクションはヘッダー、ペイロード、署名で構成されています

ヘッダーは以下のような情報があります
(キーID("kid")と、トークンの署名に使用されるアルゴリズム("alg")が含まれています)

{
  "kid": "abcdefghijklmnopqrsexample=",
  "alg": "RS256"
}

ペイロードには以下のような情報があります
(ユーザーに関する情報と、トークンの作成および有効期限のタイムスタンプが含まれています)

{
  "sub": "aaaaaaaa-bbbb-cccc-dddd-example",
  "aud": "xxxxxxxxxxxxexample",
  "email_verified": true,
  "token_use": "id",
  "auth_time": 1500009400,
  "iss": "https://cognito-idp.ap-southeast-2.amazonaws.com/ap-southeast-2_example",
  "cognito:username": "example",
  "exp": 1500013000,
  "given_name": "Example",
  "iat": 1500009400,
  "email": "example@example.com"
}

署名には、ヘッダーとペイロードの組み合わせをハッシュして暗号化したものが入ります

ヘッダーとペイロードのデコード

署名の認証周りは一旦なしにして、JWTをデコードしてトークンに含まれるヘッダとペイロードを取得する方法を説明します

Base64でエンコードされているとのことだったのでBase64でデコードすればいいやと思い、デコードしていたのですが、たまにデコードできない現象が発生してしまい、かなり苦しみました...

最終的な結論としてはBase64URLでデコードすれば間違いがありません

Base64とBase64URLの違い

Base64エンコードはデータを印字可能な64種類のデータで表現するエンコード方式です

しかし、URLの一部として利用する場合は「+」や「/」とと言ったURLセーフではない文字列が含まれるのでURLセーフにエンコードしなければなりません

そこで用いられるのがBase64URLのようです

おそらくAmplifyなどで直接JWTを取得する場合は問題ないと思いますが、リダイレクト後のURLなどからJWTをゲットした場合にURLセーフにエンコードされているのでBase64ではデコードできなかったものと考えられます

どちらにせよBase64URLでデコードすれば、ヘッダーとペイロードの値を取得できます

コーディング例

base64urlのデコーディングにはgithub.com/dvsekhvalnov/jose2go/base64urlをimportして用います
(そのほかには"strings""fmt""encoding/json"のimportが必要です)

jwt := "***.***.***" // Cognitoユーザープールから取得したJWT
// // API GatewayでLambda Proxyを用いてる場合、AuthorizationヘッダにJWTを付与するので、以下で取得可能
// jwt := events.APIGatewayProxyRequest.Headers["Authorization"]

// ドットで文字列を分割して配列にして、エンコーディングされたヘッダとペイロードを取得
headerEnc := strings.Split(jwt, ".")[0]
payloadEnc := strings.Split(jwt, ".")[1]

// base64urlを用いてヘッダーとペイロードをデコードします
headerDec, _ := base64url.Decode(headerEnc) // 第二戻り値のエラーは無視しています
payloadDec, _ := base64url.Decode(payloadEnc) // 同様

// 文字列として出力
fmt.Println(string(headerDec))
fmt.Println(string(payloadDec))

// デコードされた戻り値は[]byte型なので、以下のように構造体に入れたり、マップに入れ込んだりできます
type Header struct {
    KeyID     string `json:"kid"`
    Algorithm string `json:"alg"`
}
headerStruct := Header{}
json.Unmarshal(headerDec, &headerStruct)

headerMap := map[string]string{}
json.Unmarshal(headerDec, &headerMap)

以上のようにデコード自体は難しくないのですが、あまり載っていなかったので苦労しました

5
8
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
5
8