はじめに
今回は AWS Cognito の OpenID Connect (OIDC) を試します。
OIDCを利用しGitHubとアカウント連携します。
GitHub OpenID Connect Wrapper for Cognitoを参考に進めます。
準備
[AWS Cognitoの資料]
AWS Cognito
ユーザープールへの OIDC ID プロバイダーの追加
AWS SAM テンプレート作成
AWS SAM テンプレートで API-Gateway , Lambda, AWS Cognito の設定をします。
[参考資料]
AWS SAM テンプレートを作成する
AWS Cognito設定用のSAMテンプレート
AWS Cognito の設定は以下の部分
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: Serverless Account Page
Parameters:
ApplicationName:
Type: String
Default: 'ServerlessAccountPage'
UserPoolClientName:
Type: String
Default: 'GitHubOpenIdAuthClient'
UserPoolDefaultDomainPrefixName:
Type: String
Default: 'test-user-pool-domain2021'
UserPoolClientCallbackURL:
Type: String
Default: ''
Resources:
UserPool:
Type: AWS::Cognito::UserPool
Properties:
AdminCreateUserConfig:
AllowAdminCreateUserOnly: false
UnusedAccountValidityDays: 7
AutoVerifiedAttributes:
- 'email'
MfaConfiguration: 'OFF'
UserPoolClient:
Type: AWS::Cognito::UserPoolClient
Properties:
UserPoolId: !Ref UserPool
ClientName: !Ref UserPoolClientName
AllowedOAuthFlows:
- 'implicit'
AllowedOAuthFlowsUserPoolClient: true
AllowedOAuthScopes:
- 'openid'
ExplicitAuthFlows:
- 'ALLOW_REFRESH_TOKEN_AUTH'
GenerateSecret: false
CallbackURLs:
- !Ref UserPoolClientCallbackURL
UserPoolDomain:
Type: AWS::Cognito::UserPoolDomain
Properties:
UserPoolId: !Ref UserPool
Domain: !Ref UserPoolDefaultDomainPrefixName
Outputs:
APIURI:
Description: "Amazon Cognito Domain"
Value: !Join [ '', [ 'https://', !Ref UserPoolDomain, '.auth.', !Ref 'AWS::Region', '.amazoncognito.com'] ]
GitHub OAuth App ラッパー用のSAMテンプレート
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: Serverless Cognito GitHub OpenId Page
Globals:
Function:
Runtime: go1.x
Timeout: 15
Environment:
Variables:
REGION: !Ref 'AWS::Region'
ALGORITHM: !Ref Algorithm
KEY_ID: !Ref KeyId
CERT_PATH: !Ref CertPath
PUB_KEY_PATH: !Ref PubKeyPath
GITHUB_CLIENT_ID: !Ref GitHubClientId
GITHUB_CLIENT_SECRET: !Ref GitHubClientSecret
COGNITO_REDIRECT_URI: !Ref CognitoRedirectUri
GITHUB_API_URL: !Ref GitHubUrl
GITHUB_LOGIN_URL: !Ref GitHubLoginUrl
Parameters:
GitHubClientId:
Type: String
GitHubClientSecret:
Type: String
CognitoRedirectUri:
Type: String
GitHubUrl:
Type: String
Default: "https://api.github.com"
MinLength: 1
GitHubLoginUrl:
Type: String
Default: "https://github.com/login"
MinLength: 1
StageName:
Type: String
Default: "CognitoGitHubApiStage"
CertPath:
Type: String
Default: "jwtRS256.key"
PubKeyPath:
Type: String
Default: "jwtRS256.key.pub"
Algorithm:
Type: String
Default: "RS256"
KeyId:
Type: String
Default: "jwtRS256"
Resources:
GithubOAuthApi:
Type: AWS::Serverless::Api
Properties:
StageName: !Ref StageName
OpenApiVersion: "2.0"
OpenIdDiscovery:
Type: AWS::Serverless::Function
Properties:
CodeUri: api/discovery/bin/
Handler: main
MemorySize: 256
Events:
GetResource:
Type: Api
Properties:
Path: /.well-known/openid-configuration
Method: get
RestApiId: !Ref GithubOAuthApi
Authorize:
Type: AWS::Serverless::Function
Properties:
CodeUri: api/authorize/bin/
Handler: main
MemorySize: 256
Events:
GetResource:
Type: Api
Properties:
Path: /authorize
Method: get
RestApiId: !Ref GithubOAuthApi
Token:
Type: AWS::Serverless::Function
Properties:
CodeUri: api/token/bin/
Handler: main
MemorySize: 256
Events:
GetResource:
Type: Api
Properties:
Path: /token
Method: get
RestApiId: !Ref GithubOAuthApi
PostResource:
Type: Api
Properties:
Path: /token
Method: post
RestApiId: !Ref GithubOAuthApi
UserInfo:
Type: AWS::Serverless::Function
Properties:
CodeUri: api/userinfo/bin/
Handler: main
MemorySize: 256
Events:
GetResource:
Type: Api
Properties:
Path: /userinfo
Method: get
RestApiId: !Ref GithubOAuthApi
PostResource:
Type: Api
Properties:
Path: /userinfo
Method: post
RestApiId: !Ref GithubOAuthApi
Jwks:
Type: AWS::Serverless::Function
Properties:
CodeUri: api/jwks/bin/
Handler: main
MemorySize: 256
Events:
GetResource:
Type: Api
Properties:
Path: /.well-known/jwks.json
Method: get
RestApiId: !Ref GithubOAuthApi
Outputs:
CognitoGitHub:
Description: "Cognito GitHub Api URL"
Value: !Sub "https://${GithubOAuthApi}.execute-api.${AWS::Region}.amazonaws.com/${StageName}"
フロントエンド用のSAMテンプレート
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: Serverless Cognito OpenId FrontPage
Parameters:
ApplicationName:
Type: String
Default: 'ServerlessCognitoOpenIdFrontPage'
CognitoUrl:
Type: String
Default: 'https://your-domain-prefix.auth.your-region.amazoncognito.com'
CognitoClientId:
Type: String
Default: 'abc012'
Resources:
FrontPageApi:
Type: AWS::Serverless::HttpApi
FrontPageFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: ServerlessCognitoOpenIdFrontFunction
CodeUri: bin/
Handler: main
MemorySize: 256
Runtime: go1.x
Description: 'Cognito OpenId Front Function'
Environment:
Variables:
REGION: !Ref 'AWS::Region'
COGNITO_URL: !Ref CognitoUrl
COGNITO_CLIENT_ID: !Ref CognitoClientId
Events:
FrontPageApi:
Type: HttpApi
Properties:
Path: '/'
Method: get
ApiId: !Ref FrontPageApi
FrontProxyApi:
Type: HttpApi
Properties:
Path: '/{proxy+}'
Method: get
ApiId: !Ref FrontPageApi
Outputs:
APIURI:
Description: "URI"
Value: !Join [ '', [ 'https://', !Ref FrontPageApi, '.execute-api.',!Ref 'AWS::Region','.amazonaws.com/'] ]
Lambda関数作成
※ JWT周りの処理は github.com/dgrijalva/jwt-go を利用しました。
func getJwks()(JSONWebKey, error) {
res := JSONWebKey{}
res.Algorithm = os.Getenv("ALGORITHM")
res.KeyId = os.Getenv("KEY_ID")
res.KeyType = "RSA"
res.PublicKeyUse = "sig"
key, err := os.ReadFile(os.Getenv("PUB_KEY_PATH"))
if err != nil {
return res, err
}
verifyKey, err := jwt.ParseRSAPublicKeyFromPEM(key)
if err != nil {
return res, err
}
res.RSAModulus = joseBase64UrlEncode(verifyKey.N.Bytes())
res.RSAExponent = joseBase64UrlEncode(serializeRSAPublicExponentParam(verifyKey.E))
return res, nil
}
func joseBase64UrlEncode(b []byte) string {
return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=")
}
func serializeRSAPublicExponentParam(e int) []byte {
buf := make([]byte, 4)
binary.BigEndian.PutUint32(buf, uint32(e))
var i int
for i = 0; i < 8; i++ {
if buf[i] != 0 {
break
}
}
return buf[i:]
}
終わりに
外部サービス(GitHub)の設定を行う必要があるため、SAMテンプレートを複数に分ける必要がありました。
GitHubだけでなく他の外部サービスにも応用ができるため、今後試そうと思います。
今回JWTに触れることになり、とても勉強になりました。