カテゴリー
【Angularユーザーのための認証API自作講座②】Serverless FrameworkでCognitoオーソライザー付きRestAPIを構築する
※ 当ページには【広告/PR】を含む場合があります。
2021/06/30
2022/08/10
SLSで簡単なAPI作成
$ npm install -g serverless
$ serverless --version
Framework Core: 2.48.0
Plugin: 5.4.2
SDK: 4.2.3
Components: 3.12.0
/node_modules/.bin/
$./node_modules/.bin/sls --version
Framework Core: 2.48.0 (local)
Plugin: 5.4.2
SDK: 4.2.3
Components: 3.12.0
$ tree
.
├── package.json
├── functions
│ └── get_public.js
├── resources
│ └── functions.yml
└── serverless.yml
各ソースコードの実装
package.json
{
"name": "my_sls_learning",
"version": "0.0.1",
"scripts": {
"deploy": "serverless deploy -v",
"remove": "serverless remove -v",
"deploy:fn": "serverless deploy function -f",
"invoke": "serverless invoke --function",
"invoke:lcl": "serverless invoke local --function",
"logs": "serverless logs --function",
"pkg": "serverless package -p tmp",
},
"private": false,
"dependencies": {
"aws-sdk": "^2.7.0"
},
"devDependencies": {
"@types/aws-sdk": "^2.7.0",
"@types/node": "^15.0.0",
"serverless": "^2.0.0"
}
}
$ yarn install
serverless.yml
serverless.yml
service: my-sls-learning
frameworkVersion: "2"
provider:
name: aws
runtime: nodejs12.x
lambdaHashingVersion: 20201221
memorySize: 128
timeout: 10
stage: ${opt:stage,"dev"}
region: ap-northeast-1
httpApi:
cors: true
functions:
- ${file(./resources/functions.yml)}
package:
exclude:
- '**'
include:
- 'functions/**'
- 'package.json'
functions.yml
functions.yml
getPublic:
#👇リソースはserverless.ymlから見た相対パスで指定
handler: functions/get_public.handler
events:
- http: GET /
- http: GET /public
/
/public
get_public.js
module.exports.handler = async (event, context, callback) => {
callback(null, {
statusCode: 200,
body: JSON.stringify({
result: 'Greeting!'
}),
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': 'true'
},
})
}
余談 ~ serverless createコマンド
serverless create
aws-nodejs
$ serverless create --template aws-nodejs \
--name [新規作成するSLSサービス名] \
--path [新規作成するSLSサービスのプロジェクトフォルダ]
+ serverless.yml
+ handler.js
+ event.json
関数のローカルテスト
$ yarn invoke:lcl getPublic
#👇ローカルでレスポンスが正常に返ることを確認する
{
"statusCode": 200,
"body": "{\"result\":\"Greeting!\"}",
"headers": {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials": "true"
}
}
Done in 18.02s.
デプロイ
export AWS_ACCESS_KEY_ID="[IAMロールのID値]"
export AWS_SECRET_ACCESS_KEY: "[IAMロールのアクセスキー]"
export AWS_DEFAULT_REGION: "ap-northeast-1(例)"
$ yarn deploy
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
........
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service my-serverless-learning.zip file to S3 (52.57 KB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
..................................
Serverless: Stack update finished...
Service Information
service: my-serverless-learning
stage: dev
region: ap-northeast-1
stack: my-serverless-learning-dev
resources: 12
api keys:
None
endpoints:
GET - https://**********.execute-api.ap-northeast-1.amazonaws.com/dev
GET - https://**********.execute-api.ap-northeast-1.amazonaws.com/dev/public
functions:
getPublic: my-serverless-learning-dev-getPublic
layers:
None
**************************************************************************************************************************************
Serverless: Announcing Metrics, CI/CD, Secrets and more built into Serverless Framework. Run "serverless login" to activate for free..
**************************************************************************************************************************************
Done in 140.42s.
AdministratorAccess
関数部の部分的デプロイ
serverless deploy
$ yarn deploy:fn getPublic
Serverless: Packaging function: getPublic...
Serverless: Excluding development dependencies...
Serverless: Uploading function: getPublic (1.58 KB)...
Serverless: Successfully deployed function: getPublic
Serverless: Configuration did not change. Skipping function configuration update.
Done in 113.55s.
sls deploy
デプロイ前にアップロードされるzipの中身を確認する
tmp
$ yarn pkg
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Done in 113.05s.
#👇zipinfoコマンドで中身を確認
$ zipinfo -1 tmp/my-serverless-learning.zip
functions/get_public.js
functions/lambda.js
functions/local.js
functions/server.js
デプロイされた関数のテスト
$ yarn invoke getPublic
#👇レスポンスが正常に返ることを確認する
{
"statusCode": 200,
"body": "{\"result\":\"Greeting!\"}",
"headers": {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials": "true"
}
}
Done in 17.04s.
$ yarn logs getPublic
START RequestId: 310e2a2a-cb3c-4908-aeda-4b86d6c789ba Version: $LATEST
END RequestId: 310e2a2a-cb3c-4908-aeda-4b86d6c789ba
REPORT RequestId: 310e2a2a-cb3c-4908-aeda-4b86d6c789ba Duration: 3.64 ms Billed Duration: 4 ms Memory Size: 128 MB Max Memory Used: 64 MB Init Duration: 133.48 ms
#...
Curlでエンドポイントを叩く
$ curl --include https://*************.execute-api.ap-northeast-1.amazonaws.com/dev/public
HTTP/2 200
content-type: application/json
content-length: 22
date: Mon, 28 Jun 2021 17:48:34 GMT
x-amzn-requestid: a215270c-7ef8-4e63-89ab-f7eccb38bf56
access-control-allow-origin: *
x-amz-apigw-id: Bpa52FurNjMFjGA=
x-amzn-trace-id: Root=1-60da0b72-7111879c1c6117853b43330a;Sampled=0
access-control-allow-credentials: true
x-cache: Miss from cloudfront
x-amz-cf-pop: NRT57-C4
x-amz-cf-id: O0mXVZtOwvDwYm4oMgmhDbVdkWjX_8hy0CtMrvRX0D978UltmNSsaQ==
{"result":"Greeting!"}
Cognitoオーソライザー付きAPIの作成
既存のCognitoユーザープールからオーソライザーを作成する
Resources:
ApiGatewaySharedAuthorizer:
Type: AWS::ApiGateway::Authorizer
Properties:
Name: ApiGatewaySharedAuthorizer
RestApiId:
Ref: ApiGatewayRestApi
IdentitySource: method.request.header.Authorization
Type: COGNITO_USER_POOLS
ProviderARNs:
#👇CognitoユーザープールのARN値
- !Join
- ''
- - '[ユーザープールのARN]'
「ApiGatewayRestApi」
shared-authorizer.yml
$ tree
.
├── package.json
├── functions
│ └── get_public.js
├── resources
│ ├── shared-authorizer.yml #👈新規作成
│ └── functions.yml
└── serverless.yml
shared-authorizer.yml
serverless.yml
#...中略
resources:
#👇Cognitoオーソライザー
- ${file(resources/shared-authorizer.yml)}
#...以下略
get_private.js
$ tree
.
├── package.json
├── functions
│ ├── get_private.js #👈新規作成
│ └── get_public.js
├── resources
│ ├── shared-authorizer.yml
│ └── functions.yml
└── serverless.yml
module.exports.handler = async (event, context, callback) => {
callback(null, {
statusCode: 200,
body: JSON.stringify({
result: 'Greeting!!'
}),
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': 'true'
},
})
}
#...中略
getPrivate:
handler: functions/get_private.handler
events:
- http:
path: /private
method: get
authorizer:
type: COGNITO_USER_POOLS
authorizerId:
Ref: ApiGatewaySharedAuthorizer
invoke local
$ yarn invoke:lcl getPrivate
{
"statusCode": 200,
"body": "{\"result\":\"Greeting!!\"}",
"headers": {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Credentials": "true"
}
}
$ yarn deploy
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Uploading CloudFormation file to S3...
#...中略
endpoints:
GET - https://**********.execute-api.ap-northeast-1.amazonaws.com/dev
GET - https://**********.execute-api.ap-northeast-1.amazonaws.com/dev/public
GET - https://**********.execute-api.ap-northeast-1.amazonaws.com/dev/private
functions:
getPublic: my-serverless-learning-dev-getPublic
getPrivate: my-serverless-learning-dev-getPrivate
layers:
None
#...
https://**********.execute-api.ap-northeast-1.amazonaws.com/dev/private
$ curl --include https://*********.execute-api.ap-northeast-1.amazonaws.com/dev/private \
-H "Authorization:[正しいIdトークン]"
HTTP/2 200
content-type: application/json
#...中略
{"result":"Greeting!!"}
$ curl --include https://*********.execute-api.ap-northeast-1.amazonaws.com/dev/private \
-H "Authorization:[間違ったIdトークン]"
HTTP/2 401
content-type: application/json
#...中略
{"message":"Unauthorized"}
新規作成したCognitoユーザープールでオーソライザーを作成する
shared-authorizer.yml
serverless.yml
#...中略
resources:
#👇前のCognitoオーソライザーはコメントアウト
#- ${file(resources/shared-authorizer.yml)}
#👇新しいCognitoオーソライザー
- ${file(resources/cognito-authorizer.yml)}
#👇Cognitoユーザープール構築用
- ${file(resources/cognito-user-pool.yml)}
#...以下略
cognito-user-pool.yml
Resources:
#👇新規Cognito User Pool作成の宣言
MySLSAuthUserPool:
Type: 'AWS::Cognito::UserPool'
Properties:
#👇ユーザーパスワード復旧方法
AccountRecoverySetting:
RecoveryMechanisms:
- Name: 'verified_email'
Priority: 1
#👇管理者権限を持つLambdaでのみユーザー作成
AdminCreateUserConfig:
#👇ユーザーがSDKでサインアップする場合はfalse
AllowAdminCreateUserOnly: false
InviteMessageTemplate:
EmailMessage: 'Your username is {username} and temporary password is {####}.'
EmailSubject: 'Your temporary password'
SMSMessage: 'Your username is {username} and temporary password is {####}.'
AliasAttributes:
- email
- preferred_username
#👇ユーザーのサインアップ方法(EmailかSMSを選択)
AutoVerifiedAttributes:
#👇今回はEmailタイプを指定
- email
DeviceConfiguration:
#👇ユーザーデバイスは記憶しない
ChallengeRequiredOnNewDevice: false
DeviceOnlyRememberedOnUserPrompt: false
EmailConfiguration:
EmailSendingAccount: COGNITO_DEFAULT
EmailVerificationMessage: 'Your verification code is {####}.'
EmailVerificationSubject: 'Your verification code'
#👇MFA認証は利用しない
MfaConfiguration: OFF
#👇パスワードの強さを設定
Policies:
PasswordPolicy:
MinimumLength: 8
RequireLowercase: false
RequireNumbers: false
RequireSymbols: false
RequireUppercase: false
#👇一時パスワードはとりあえず最長の1年で設定
TemporaryPasswordValidityDays: 365
Schema:
- AttributeDataType: String
DeveloperOnlyAttribute: false
#👇IDプロバイダを追加し属性マッピングを拡張する場合には変更可能に設定
Mutable: true
Name: email
Required: true
SmsAuthenticationMessage: 'Your verification code is {####}.'
SmsVerificationMessage: 'Your verification code is {####}.'
UsernameConfiguration:
CaseSensitive: true
UserPoolAddOns:
#👇CloudWatchにてログ確認のみに設定
AdvancedSecurityMode: AUDIT
#👇ユーザープールの名前(サービス名&ステージ付き)を指定
UserPoolName: ${self:service}-${self:provider.stage}-user-pool
UserPoolTags:
Service: ${self:service}-${self:provider.stage}
VerificationMessageTemplate:
#👇認証リンクなしでコード送信のみ
DefaultEmailOption: CONFIRM_WITH_CODE
EmailMessage: 'Your verification code is {####}.'
EmailSubject: 'Your verification code'
SmsMessage: 'Your verification code is {####}.'
#👇アプリクライアントの設定
MySLSAuthUserPoolClient:
Type: AWS::Cognito::UserPoolClient
Properties:
#👇クライアント名(ステージ別)
ClientName: ${self:service}-${self:provider.stage}-user-pool-client
#👇認証フローの指定
ExplicitAuthFlows:
- ALLOW_ADMIN_USER_PASSWORD_AUTH
- ALLOW_CUSTOM_AUTH
- ALLOW_USER_SRP_AUTH
- ALLOW_REFRESH_TOKEN_AUTH
GenerateSecret: false
#👇Cognitoから返却するエラー内容を返す
PreventUserExistenceErrors: ENABLED
ReadAttributes:
- email
- preferred_username
#👇リフレッシュトークンの生存日数
RefreshTokenValidity: 10
SupportedIdentityProviders:
- COGNITO
#👇どのPoolに帰属するClientかをRef関数を使って先ほど設定したPoolと関連付け
UserPoolId:
Ref: MySLSAuthUserPool
#👇外部のIDプロバイダー利用時に以下の属性情報の書き込みを有効化
WriteAttributes:
- email
- preferred_username
cognito-authorizer.yml
Fn::GetAtt
Resources:
MySLSAuthAuthorizer:
Type: AWS::ApiGateway::Authorizer
Properties:
Name: MySLSAuthAuthorizer
RestApiId:
Ref: ApiGatewayRestApi
IdentitySource: method.request.header.Authorization
Type: COGNITO_USER_POOLS
ProviderARNs:
- { Fn::GetAtt: [MySLSAuthUserPool, Arn] }
functions.ymlの修正
#...中略
getPrivate:
handler: functions/get_private.handler
events:
- http:
path: /private
method: get
# integration: lambda ... x
# integration: lambda-proxy も可
authorizer:
type: COGNITO_USER_POOLS
authorizerId:
# Ref: ApiGatewaySharedAuthorizer
Ref: MySLSAuthAuthorizer
integration: lambda-proxy
integration: lambda
デプロイ
$ yarn deploy
$ curl --include https://************.execute-api.ap-northeast-1.amazonaws.com/dev/public
HTTP/2 200
content-type: application/json
#...中略
{"result":"Greeting!"}
$ curl --include https://************.execute-api.ap-northeast-1.amazonaws.com/dev/private \
-H "Authorization:[正しいIdトークン]"
HTTP/2 200
content-type: application/json
#...中略
{"result":"Greeting!!"}
$ curl --include https://************.execute-api.ap-northeast-1.amazonaws.com/dev/private \
-H "Authorization:[間違ったIdトークン]"
HTTP/2 401
content-type: application/json
#...中略
{"message":"Unauthorized"}
まとめ
参考サイト
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー