Cognitoをgolnagから操作してみる
イマイチ理解しきれてなかったので、ラッパーライブラリを作りつつ、勉強してみました。
元ネタはhttps://dev.classmethod.jp/articles/get-aws-temporary-security-credentials-with-cognito-id-pool-by-aws-cli
で、AWS CLIベースの記事だったのをgolangに書き起こしつつ理解するって形を取りました。
作ったラッパーは
https://gitlab.com/kuritayu/cognito
におきました。
ユーザプール、IDプールの違い
- ユーザプール: ユーザ情報、ログイン情報の管理(認証担当)
- IDプール: ユーザに応じた一時クレデンシャルキーの発行(認可担当)
パターン別のクレデンシャルキーの発行
- 認証済・非認証に応じたクレデンシャルキーの発行(IAM Role)
- ルールベースのクレデンシャルキーの発行
- ユーザプールに設定したIAM Roleでクレデンシャルキーを発行
環境構築
上記クラスメソッドさんの記事に詳細に記載されているため、割愛
認証部品(ユーザプール操作)
初期化
セッション情報、プールID、アプリクライアントIDを引数に、構造体を構築します。
region := "region名"
poolId := "ユーザプールID"
clientId := "アプリクライアントID"
sess := session.Must(
session.NewSession(&aws.Config{
Region: aws.String(region),
}))
userPool := New(sess, poolId, clientId)
ユーザ作成
saveメソッドを呼び出すだけです。
今回のユーザプールでは必須属性としてメールアドレス、カスタム属性として会社名を入れてみました。
func TestUserPool_Save(t *testing.T) {
err := userPool.Save("ユーザ名", "メールアドレス", "会社名")
assert.NoError(t, err)
}
saveメソッドでは、AttributeTypeにemailやカスタム属性の会社名を設定しています。
func (p UserPool) Save(name string, email string, company string) error {
var attrs []*cognitoidentityprovider.AttributeType
emailAttr := p.userAttribute(cognitoidentityprovider.UsernameAttributeTypeEmail, email)
attrs = append(attrs, emailAttr)
companyAttr := p.userAttribute("custom:company", company)
attrs = append(attrs, companyAttr)
input := &cognitoidentityprovider.AdminCreateUserInput{
UserPoolId: aws.String(p.PoolId),
Username: aws.String(name),
UserAttributes: attrs,
}
_, err := p.client.AdminCreateUser(input)
if err != nil {
return err
}
return nil
}
ユーザ情報取得
ユーザ名を引数にとり、構造体で返します。
func TestUserPool_FindOne(t *testing.T) {
out, err := userPool.FindOne("ユーザ名")
assert.NoError(t, err)
}
FindOneメソッドですが、UserAttributesが配列で入っていて厄介です。
ループで回して構造体に入れていますが、他にいい方法があるのかも。
func (p UserPool) FindOne(name string) (*UserInfo, error) {
input := &cognitoidentityprovider.AdminGetUserInput{
UserPoolId: aws.String(p.PoolId),
Username: aws.String(name),
}
out, err := p.client.AdminGetUser(input)
if err != nil {
return nil, err
}
userInfo := &UserInfo{
Name: name,
}
for _, a := range out.UserAttributes {
switch aws.StringValue(a.Name) {
case "sub":
userInfo.Uuid = aws.StringValue(a.Value)
case "email":
userInfo.Email = aws.StringValue(a.Value)
case "custom:company":
userInfo.Company = aws.StringValue(a.Value)
}
}
return userInfo, nil
}
認証
こちらもシンプルにID/Passwordを引数にわたすだけです。IDトークンが返ってきます。(IDプールで使うので)
func TestUserPool_Authenticate(t *testing.T) {
out, err := userPool.Authenticate("ユーザ名", "パスワード")
assert.NoError(t, err)
}
Authenticateメソッドは、凝ったことはしていません。
func (p UserPool) Authenticate(name string, password string) (string, error) {
input := &cognitoidentityprovider.AdminInitiateAuthInput{
AuthFlow: aws.String("ADMIN_USER_PASSWORD_AUTH"),
AuthParameters: map[string]*string{
"USERNAME": aws.String(name),
"PASSWORD": aws.String(password),
},
ClientId: aws.String(p.clientId),
UserPoolId: aws.String(p.PoolId),
}
out, err := p.client.AdminInitiateAuth(input)
if err != nil {
return "", err
}
return aws.StringValue(out.AuthenticationResult.IdToken), nil
}
認可部品(IDプール操作)
初期化
セッション情報、プールIDを引数に、構造体を構築します。
region := "リージョン名"
poolId := "IDプール名"
sess := session.Must(
session.NewSession(&aws.Config{
Region: aws.String(region),
}))
idPool := New(sess, poolId)
Identity ID取得
認証済、非認証で2パターン用意しました。
ユーザプールから取得したIDトークンを使うかどうか、の違いですね。
func TestIdPool_GetIdWithoutAuth(t *testing.T) {
out, err := idPool.GetIdWithoutAuth()
assert.NoError(t, err)
}
func TestIdPool_GetIdWithAuth(t *testing.T) {
userPool := SetUpForUserPool()
idToken, err := userPool.Authenticate("ユーザ名", "パスワード")
assert.NoError(t, err)
identityId, err := idPool.GetIdWithAuth(idToken, userPool.PoolId)
assert.NoError(t, err)
}
メソッドとしては、インプットのパラメータが違うだけです。
投げるリクエストは共通化しています。
func (i IdPool) GetIdWithoutAuth() (string, error) {
input := &cognitoidentity.GetIdInput{
IdentityPoolId: aws.String(i.poolId),
}
return i.doGetId(input)
}
func (i IdPool) GetIdWithAuth(token string, userPoolId string) (string, error) {
param := make(map[string]*string)
provider := fmt.Sprintf("cognito-idp.%v.amazonaws.com/%v", aws.StringValue(i.client.Config.Region), userPoolId)
param[provider] = aws.String(token)
input := &cognitoidentity.GetIdInput{
IdentityPoolId: aws.String(i.poolId),
Logins: param,
}
return i.doGetId(input)
}
func (i IdPool) doGetId(input *cognitoidentity.GetIdInput) (string, error) {
out, err := i.client.GetId(input)
if err != nil {
return "", err
}
return aws.StringValue(out.IdentityId), nil
}
認可(クレデンシャルキー発行)
取得したクレデンシャルキーをstsでチェックし、割り当てられていることまで確認しました。
func TestIdPool_GetCredentialWithoutAuth(t *testing.T) {
idPool := SetUpForIdPool()
identityId, _ := idPool.GetIdWithoutAuth()
out, err := idPool.GetCredentialWithoutAuth(identityId)
assert.NoError(t, err)
sts, err := SetUpSTS(out.accessKey, out.secretKey, out.sessionToken)
assert.NoError(t, err)
log.Print(sts)
}
func TestIdPool_GetCredentialWithAuth(t *testing.T) {
userPool := SetUpForUserPool()
idToken, err := userPool.Authenticate("ユーザ名", "パスワード")
assert.NoError(t, err)
idPool := SetUpForIdPool()
identityId, err := idPool.GetIdWithAuth(idToken, userPool.PoolId)
assert.NoError(t, err)
out, err := idPool.GetCredentialWithAuth(identityId, idToken, userPool.PoolId)
assert.NoError(t, err)
sts, err := SetUpSTS(out.accessKey, out.secretKey, out.sessionToken)
assert.NoError(t, err)
log.Print(sts)
}