GolangでCognito認証をしてトークンをjwt形式にする


## できること
認証はuserPool、認可はIdentitiyPoolで行われ、それぞれ別のリソース

### 認証
- Cognitoで管理されているユーザー情報で認証ができる
- 外部プロバイダー（Azure、Google、Facebook）と連携が可能
- ユーザー認証が成功すると、トークンが発行され、アプリケーション側でトークンの検証を行うことで認証機能をつくることができる
- UserPoolで行われる

### 認可
- 一時的な権限を与える
- チケットを渡すイメージ
- Cognitoではグループや属性ごとにIAMRoleを割り振ったりすることができる
- IndentityPoolで行われる


## Cognito設定
- `ALLOW_ADMIN_USER_PASSWORD_AUTH`にチェックが必要
- AppClient作成時に、ClientSecretを使うのチェックを外す
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/293481/e0e599e6-15ea-681e-2e16-d5d41c1306d1.png)


## サンプルコード

```main.go
package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"os"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/cognitoidentityprovider"
	"github.com/golang-jwt/jwt/v4"
)

type Tokens struct {
	Id      string
	Access  string
	Refresh string
}

func main() {
	tokens := cognitoAuth()
	inspectionjwt(tokens.Id)
}

func inspectionjwt(tokenString string) {

	CustomClaims := jwt.MapClaims{}

	// tokenからjwt形式へ変換する
	token, _ := jwt.ParseWithClaims(tokenString, CustomClaims, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, errors.New("unexpected signing method")
		}
		return []byte("test"), nil
	})

	// 一度JSONへ変換
	jsonString, err := json.Marshal(token.Claims)
	if err != nil {
		fmt.Println("JSON Marshal error: ", err)
	}

	// Cognitoから返されるTokenの構造に合わせてStructを定義する
	type JwtClaims struct {
		Email string `json:"email"`
		User  string `json:"cognito:username"`
	}

	var jwtClaims JwtClaims

	// 定義したStructへ変換
	if err := json.Unmarshal(jsonString, &jwtClaims); err != nil {
		fmt.Println("jsonunmarshal error")
	}

	// メールアドレス、名前を表示
	fmt.Println(jwtClaims.Email)
	fmt.Println(jwtClaims.User)
}

func cognitoAuth() Tokens {
	if len(os.Args) != 3 {
		log.Fatal("invalid args")
	}
	name := os.Args[1]
	password := os.Args[2]

	params := &cognitoidentityprovider.AdminInitiateAuthInput{
		AuthFlow: aws.String(cognitoidentityprovider.AuthFlowTypeAdminNoSrpAuth),
		AuthParameters: map[string]*string{
			"USERNAME": aws.String(name),
			"PASSWORD": aws.String(password),
		},
		ClientId:   aws.String(os.Getenv("CLIENT_ID")),
		UserPoolId: aws.String(os.Getenv("USER_POOL_ID")),
	}

	client := cognitoidentityprovider.New(
		session.Must(session.NewSessionWithOptions(session.Options{
			SharedConfigState: session.SharedConfigEnable,
		})),
	)

	res, err := client.AdminInitiateAuth(params)
	if err != nil {
		log.Print("AdminAuth Error")
		log.Fatal(err)
	}
	if res == nil || res.AuthenticationResult == nil || res.AuthenticationResult.IdToken == nil {
		log.Fatal("failed to login")
	}

	var tokens Tokens

	tokens.Id = *res.AuthenticationResult.IdToken
	tokens.Access = *res.AuthenticationResult.AccessToken
	tokens.Refresh = *res.AuthenticationResult.RefreshToken

	return tokens
}

```

### 実行
```golang
go run main.go yuta password
```

出力

```
yuta@vamdemicsystem.black
kujirai
```

## トークン
IDトークン、Accessトークン、リフレッシュトークンの3つが発行される
### IDトークン
- 認証されたユーザーの情報がJWT形式で含まれている
- グループや属性などを拾うことができる
- デフォルト1時間でExpireする
- アプリケーションで認証機能を作る場合は、基本的にこれを使う

### アクセストークン
- 自分のグループや属性などプロパティ的なものを変更するために必要なトークン
- 認証されたユーザーに関するクレーム、グループのリスト、スコープのリストなどが含まれる
- IDトークンと同様にJWTで表現される
- IDトークンとは署名するキーが異なるため、Kidが異なる
- デフォルト1時間でExpireする
- https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/amazon-cognito-user-pools-using-the-access-token.html

### リフレッシュートークン
- IDトークンとアクセストークンを再発行できるトークン
- 数時間~数日単位で期限を設定することができる
- トークンが切れるとユーザーは再認証を求められることになるが、リフレッシュートークンを利用するとこでユーザーに操作させることなく、トークンを再発行させることができる


## 参考
https://tech.anti-pattern.co.jp/golang-cognito/
https://dev.classmethod.jp/articles/study-tokens-of-cognito-user-pools/
https://zenn.dev/takamin55/articles/8442a7583fe5fa