背景
Cognito + API Gateway + LambdaでSAMを使い認証付きAPIを構築する際、「全エンドポイントに認証をかけつつ、特定のエンドポイントだけ認証なしにする」という制御が、SAMでは驚くほど簡潔に書けました。
1. DefaultAuthorizerで全エンドポイントに一括適用
MyApi:
Type: AWS::Serverless::Api
Properties:
StageName: Prod
Auth:
DefaultAuthorizer: CognitoAuthorizer
Authorizers:
CognitoAuthorizer:
UserPoolArn: !GetAtt UserPool.Arn
Identity:
Header: Authorization
Auth.DefaultAuthorizerを指定すると、このAPI配下の全エンドポイントにデフォルトでCognito認証が適用されます。コンソールではエンドポイントごとに「オーソライザーを選択」する操作が必要ですが、SAMでは1箇所の宣言で完結します。
2. 特定エンドポイントだけAuthorizer: NONEで除外する
Events:
PublicEndpoint: # /hello: 公開エンドポイント
Type: Api
Properties:
Path: /hello
Method: get
Auth:
Authorizer: NONE # DefaultAuthorizer を明示的に無効化
PrivateEndpoint: # /profile: 認証必須
Type: Api
Properties:
Path: /profile
Method: get
# Auth 指定なし → DefaultAuthorizer が自動適用
認証を除外したいエンドポイントにだけAuthorizer: NONEを明示し、それ以外はAuthの指定を省略するだけでDefaultAuthorizerが自動適用されます。「原則すべて認証必須、例外だけ明示する」という設計思想がコード上にそのまま表れる書き方です。
3. UserPoolArn: !GetAtt UserPool.ArnでCognitoとAuthorizerが自動連携
Authorizers:
CognitoAuthorizer:
UserPoolArn: !GetAtt UserPool.Arn
Identity:
Header: Authorization
コンソールではAuthorizer作成画面でUser Poolをドロップダウンから手動選択し、「トークンのソース」欄(デフォルト空欄)にAuthorizationと入力する必要がありますが、SAMでは!GetAttによるリソース参照とIdentity.Headerの指定だけで同じ設定が完結します。空欄入力忘れによる「全リクエストが401になる」という事故が起こりません。
4. GenerateSecret: falseでCLIテスト用クライアントを明示制御
UserPoolClient:
Type: AWS::Cognito::UserPoolClient
Properties:
GenerateSecret: false # シークレットなし(CLI テスト用)
ExplicitAuthFlows:
- ALLOW_USER_PASSWORD_AUTH # username/password で直接認証(テスト用)
- ALLOW_REFRESH_TOKEN_AUTH
コンソールの新UIでは「従来のウェブアプリケーション」を選ぶとシークレットが自動付与されてしまい、別途SPAタイプのクライアントを作り直す必要がありました。SAMではGenerateSecret: falseを明示するだけで、最初から意図通りのクライアントが1回のデプロイで得られます。
まとめ
DefaultAuthorizerとAuthorizer: NONEの組み合わせで「原則認証必須、例外を明示」という設計をコードで表現できる点が、このハンズオンの核心でした。コンソール版で発生しやすい「トークンのソース入力忘れ」や「シークレット付きクライアントの誤作成」といった事故を、SAMは構造的に防いでくれます。