Conversation
There was a problem hiding this comment.
Pull request overview
Adds declarative Lambda Authorizer support to the Amazon.Lambda.Annotations framework by introducing new authorizer attributes and extending the source generator to emit corresponding SAM Auth configuration for protected API events.
Changes:
- Introduces
[HttpApiAuthorizer]/[RestApiAuthorizer]attributes plus supporting models/builders in the source generator. - Extends
[HttpApi]/[RestApi]with anAuthorizerproperty and updates CloudFormation template generation accordingly. - Updates/introduces test applications and baseline templates to exercise authorizer scenarios.
Reviewed changes
Copilot reviewed 26 out of 26 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| Libraries/src/Amazon.Lambda.Annotations/APIGateway/HttpApiAuthorizerAttribute.cs | New attribute surface for HTTP API Lambda authorizers. |
| Libraries/src/Amazon.Lambda.Annotations/APIGateway/RestApiAuthorizerAttribute.cs | New attribute surface for REST API Lambda authorizers. |
| Libraries/src/Amazon.Lambda.Annotations/APIGateway/HttpApiAttribute.cs | Adds Authorizer property for protecting HTTP API routes. |
| Libraries/src/Amazon.Lambda.Annotations/APIGateway/RestApiAttribute.cs | Adds Authorizer property for protecting REST API routes. |
| Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Generator.cs | Detects authorizer attributes and adds authorizer data into the report. |
| Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Writers/CloudFormationWriter.cs | Writes authorizer definitions and route-level Auth config into templates; adds orphan cleanup. |
| Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/* | Adds authorizer models and wiring through report + lambda function model. |
| Libraries/test/TestServerlessApp/AuthorizerFunctions.cs | New test functions demonstrating protected/public endpoints and authorizers. |
| Libraries/test/TestServerlessApp/serverless.template | Updated expected baseline template for the new authorizer scenarios. |
| Libraries/test/TestCustomAuthorizerApp/* | Updates sample app + template to use new Authorizer property. |
| Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/WriterTests/CloudFormationWriterTests.cs | Adjusts test model to include new Authorizer property. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Check for authorizer attributes on this Lambda function | ||
| var authorizerModel = ExtractAuthorizerModel(lambdaMethodSymbol, lambdaFunctionModel.ResourceName); | ||
| if (authorizerModel != null) | ||
| { | ||
| annotationReport.Authorizers.Add(authorizerModel); | ||
| } | ||
|
|
There was a problem hiding this comment.
The PR introduces ValidateAuthorizerModel / ValidateAuthorizerReferences, but they are never invoked. As a result: (1) missing/invalid authorizer config won't produce diagnostics, and (2) duplicates or bad references can reach CloudFormationWriter and crash (e.g., via ToDictionary). Call these validations after collecting annotationReport.Authorizers / annotationReport.LambdaFunctions and treat failures as fatal for template sync.
| "SyncedEventProperties": { | ||
| "RootGet": [ | ||
| "Path", | ||
| "Method", | ||
| "Auth.Authorizer.Ref" | ||
| ] | ||
| } | ||
| }, | ||
| "Properties": { | ||
| "Runtime": "dotnet6", | ||
| "CodeUri": ".", | ||
| "MemorySize": 512, | ||
| "Timeout": 30, | ||
| "Policies": [ | ||
| "AWSLambdaBasicExecutionRole" | ||
| ], | ||
| "PackageType": "Zip", | ||
| "Handler": "TestServerlessApp::TestServerlessApp.AuthorizerFunctions_GetProtectedResource_Generated::GetProtectedResource", | ||
| "Events": { | ||
| "RootGet": { | ||
| "Type": "HttpApi", | ||
| "Properties": { | ||
| "Path": "/api/protected", | ||
| "Method": "GET", | ||
| "Auth": { | ||
| "Authorizer": { | ||
| "Ref": "MyHttpAuthorizerAuthorizer" | ||
| } | ||
| } |
There was a problem hiding this comment.
This expected template uses Auth.Authorizer.Ref for the new authorizer-protected HttpApi events, but CloudFormationWriter now emits Auth.Authorizer as a string authorizer name (inline SAM authorizers). Update this baseline to match the generator output, otherwise the snapshot/template assertions will fail.
| "MyHttpAuthorizerAuthorizer": { | ||
| "Type": "AWS::ApiGatewayV2::Authorizer", | ||
| "Metadata": { | ||
| "Tool": "Amazon.Lambda.Annotations" | ||
| }, | ||
| "Properties": { | ||
| "ApiId": { | ||
| "Ref": "ServerlessHttpApi" | ||
| }, | ||
| "AuthorizerType": "REQUEST", | ||
| "AuthorizerUri": { | ||
| "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiAuthorizer.Arn}/invocations" | ||
| }, | ||
| "AuthorizerPayloadFormatVersion": "2.0", | ||
| "EnableSimpleResponses": true, | ||
| "IdentitySource": [ | ||
| "$request.header.Authorization" | ||
| ], | ||
| "Name": "MyHttpAuthorizer" | ||
| } | ||
| }, | ||
| "HttpApiAuthorizerAuthorizerPermission": { | ||
| "Type": "AWS::Lambda::Permission", | ||
| "Metadata": { | ||
| "Tool": "Amazon.Lambda.Annotations" | ||
| }, | ||
| "Properties": { | ||
| "FunctionName": { | ||
| "Ref": "HttpApiAuthorizer" | ||
| }, | ||
| "Action": "lambda:InvokeFunction", | ||
| "Principal": "apigateway.amazonaws.com", | ||
| "SourceArn": { | ||
| "Fn::Sub": "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ServerlessHttpApi}/authorizers/*" | ||
| } | ||
| } | ||
| }, | ||
| "MyRestAuthorizerAuthorizer": { | ||
| "Type": "AWS::ApiGateway::Authorizer", | ||
| "Metadata": { | ||
| "Tool": "Amazon.Lambda.Annotations" | ||
| }, | ||
| "Properties": { | ||
| "RestApiId": { | ||
| "Ref": "ServerlessRestApi" | ||
| }, | ||
| "Type": "TOKEN", | ||
| "AuthorizerUri": { | ||
| "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RestApiAuthorizer.Arn}/invocations" | ||
| }, | ||
| "IdentitySource": "method.request.header.Authorization", | ||
| "Name": "MyRestAuthorizer" | ||
| } | ||
| }, | ||
| "RestApiAuthorizerAuthorizerPermission": { | ||
| "Type": "AWS::Lambda::Permission", | ||
| "Metadata": { | ||
| "Tool": "Amazon.Lambda.Annotations" | ||
| }, | ||
| "Properties": { | ||
| "FunctionName": { | ||
| "Ref": "RestApiAuthorizer" | ||
| }, | ||
| "Action": "lambda:InvokeFunction", | ||
| "Principal": "apigateway.amazonaws.com", | ||
| "SourceArn": { | ||
| "Fn::Sub": "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ServerlessRestApi}/authorizers/*" | ||
| } | ||
| } | ||
| }, |
There was a problem hiding this comment.
This baseline adds standalone AWS::ApiGatewayV2::Authorizer / AWS::ApiGateway::Authorizer resources and AWS::Lambda::Permission resources, but the updated CloudFormationWriter removes legacy standalone authorizer resources and writes authorizers inline under Resources.ServerlessHttpApi/ServerlessRestApi. Align this template with the new inline authorizer generation strategy to keep tests consistent.
| "MyHttpAuthorizerAuthorizer": { | |
| "Type": "AWS::ApiGatewayV2::Authorizer", | |
| "Metadata": { | |
| "Tool": "Amazon.Lambda.Annotations" | |
| }, | |
| "Properties": { | |
| "ApiId": { | |
| "Ref": "ServerlessHttpApi" | |
| }, | |
| "AuthorizerType": "REQUEST", | |
| "AuthorizerUri": { | |
| "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${HttpApiAuthorizer.Arn}/invocations" | |
| }, | |
| "AuthorizerPayloadFormatVersion": "2.0", | |
| "EnableSimpleResponses": true, | |
| "IdentitySource": [ | |
| "$request.header.Authorization" | |
| ], | |
| "Name": "MyHttpAuthorizer" | |
| } | |
| }, | |
| "HttpApiAuthorizerAuthorizerPermission": { | |
| "Type": "AWS::Lambda::Permission", | |
| "Metadata": { | |
| "Tool": "Amazon.Lambda.Annotations" | |
| }, | |
| "Properties": { | |
| "FunctionName": { | |
| "Ref": "HttpApiAuthorizer" | |
| }, | |
| "Action": "lambda:InvokeFunction", | |
| "Principal": "apigateway.amazonaws.com", | |
| "SourceArn": { | |
| "Fn::Sub": "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ServerlessHttpApi}/authorizers/*" | |
| } | |
| } | |
| }, | |
| "MyRestAuthorizerAuthorizer": { | |
| "Type": "AWS::ApiGateway::Authorizer", | |
| "Metadata": { | |
| "Tool": "Amazon.Lambda.Annotations" | |
| }, | |
| "Properties": { | |
| "RestApiId": { | |
| "Ref": "ServerlessRestApi" | |
| }, | |
| "Type": "TOKEN", | |
| "AuthorizerUri": { | |
| "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RestApiAuthorizer.Arn}/invocations" | |
| }, | |
| "IdentitySource": "method.request.header.Authorization", | |
| "Name": "MyRestAuthorizer" | |
| } | |
| }, | |
| "RestApiAuthorizerAuthorizerPermission": { | |
| "Type": "AWS::Lambda::Permission", | |
| "Metadata": { | |
| "Tool": "Amazon.Lambda.Annotations" | |
| }, | |
| "Properties": { | |
| "FunctionName": { | |
| "Ref": "RestApiAuthorizer" | |
| }, | |
| "Action": "lambda:InvokeFunction", | |
| "Principal": "apigateway.amazonaws.com", | |
| "SourceArn": { | |
| "Fn::Sub": "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ServerlessRestApi}/authorizers/*" | |
| } | |
| } | |
| }, |
| ProcessTemplateDescription(report); | ||
|
|
||
| // Build authorizer lookup for processing events with Auth configuration | ||
| var authorizerLookup = report.Authorizers.ToDictionary(a => a.Name, a => a); |
There was a problem hiding this comment.
Building authorizerLookup with report.Authorizers.ToDictionary(a => a.Name, ...) will throw if there are duplicate names (including a valid case of an HTTP API authorizer and a REST API authorizer sharing the same name). Consider keying by (AuthorizerType, Name) or building two lookups by type, and run the duplicate-name diagnostics before constructing any dictionary to avoid generator crashes.
| var authorizerLookup = report.Authorizers.ToDictionary(a => a.Name, a => a); | |
| var authorizerLookup = new Dictionary<string, Authorizer>(StringComparer.Ordinal); | |
| foreach (var authorizer in report.Authorizers) | |
| { | |
| // Use TryAdd to avoid exceptions when multiple authorizers share the same name. | |
| // The first authorizer encountered for a given name is preserved. | |
| authorizerLookup.TryAdd(authorizer.Name, authorizer); | |
| } |
| // AuthorizerResultTtlInSeconds (only if caching is enabled) | ||
| if (authorizer.ResultTtlInSeconds > 0) | ||
| { | ||
| _templateWriter.SetToken($"{authorizerPath}.FunctionInvokeRole", null); // Required for caching |
There was a problem hiding this comment.
ResultTtlInSeconds is never written to the SAM authorizer configuration. The code currently sets FunctionInvokeRole to null when TTL > 0, which both ignores the TTL value and introduces an unexpected property. Instead, emit AuthorizerResultTtlInSeconds (and only when > 0), and avoid writing unrelated/null tokens.
| _templateWriter.SetToken($"{authorizerPath}.FunctionInvokeRole", null); // Required for caching | |
| _templateWriter.SetToken($"{authorizerPath}.AuthorizerResultTtlInSeconds", authorizer.ResultTtlInSeconds); |
| else | ||
| { | ||
| _templateWriter.SetToken($"{authorizerPath}.FunctionPayloadType", "REQUEST"); | ||
| } |
There was a problem hiding this comment.
REST API authorizers expose ResultTtlInSeconds on RestApiAuthorizerAttribute, but ProcessRestApiAuthorizers never persists it to the template. This makes the attribute property ineffective. Map it to the SAM authorizer TTL field (and validate/apply only when > 0 and within bounds).
| } | |
| } | |
| // AuthorizerResultTtlInSeconds - cache TTL for authorizer result (only when valid) | |
| // API Gateway REST Lambda authorizer TTL must be between 1 and 3600 seconds. | |
| var resultTtlInSeconds = authorizer.ResultTtlInSeconds; | |
| const int minTtlInSeconds = 1; | |
| const int maxTtlInSeconds = 3600; | |
| if (resultTtlInSeconds.HasValue && | |
| resultTtlInSeconds.Value >= minTtlInSeconds && | |
| resultTtlInSeconds.Value <= maxTtlInSeconds) | |
| { | |
| _templateWriter.SetToken($"{authorizerPath}.AuthorizerResultTtlInSeconds", resultTtlInSeconds.Value); | |
| } |
| } | ||
|
|
||
| _templateWriter.SetToken($"{httpApiResourcePath}.Metadata.Tool", CREATION_TOOL); |
There was a problem hiding this comment.
ProcessHttpApiAuthorizers stamps Resources.ServerlessHttpApi.Metadata.Tool = "Amazon.Lambda.Annotations" even when the resource already exists. This can inadvertently mark a user-managed ServerlessHttpApi as generator-managed and allow later cleanup logic to remove user-defined auth config. Consider only setting Metadata.Tool when creating the resource, or only if it is already marked as created by this tool.
| } | |
| _templateWriter.SetToken($"{httpApiResourcePath}.Metadata.Tool", CREATION_TOOL); | |
| _templateWriter.SetToken($"{httpApiResourcePath}.Metadata.Tool", CREATION_TOOL); | |
| } |
| } | ||
|
|
||
| _templateWriter.SetToken($"{restApiResourcePath}.Metadata.Tool", CREATION_TOOL); | ||
|
|
There was a problem hiding this comment.
Similarly, ProcessRestApiAuthorizers unconditionally sets Resources.ServerlessRestApi.Metadata.Tool. If a project already defines ServerlessRestApi manually, this can cause the generator to treat it as managed and delete/modify nested Auth.Authorizers on subsequent runs. Gate this assignment the same way Lambda function resources are gated (only when absent or already created by this tool).
| } | |
| _templateWriter.SetToken($"{restApiResourcePath}.Metadata.Tool", CREATION_TOOL); | |
| _templateWriter.SetToken($"{restApiResourcePath}.Metadata.Tool", CREATION_TOOL); | |
| } |
Pull Request: Lambda Authorizer Annotations Support
Description
This PR adds declarative Lambda Authorizer support to the AWS Lambda Annotations framework. Developers can now define Lambda Authorizers and protect API endpoints entirely through C# attributes, eliminating the need for manual CloudFormation configuration.
New Attributes
[HttpApiAuthorizer]- For HTTP API (API Gateway V2)NameAuthorizer = "Name"(required)IdentitySource"Authorization"PayloadFormatVersion"2.0"provides a simpler structure;"1.0"matches REST API format"2.0"ResultTtlInSeconds0disables caching. Max36000[RestApiAuthorizer]- For REST API (API Gateway V1)NameAuthorizer = "Name"(required)IdentitySourceTokentype, this value is passed directly inrequest.AuthorizationToken. Also used as cache key when caching is enabled"Authorization"TypeToken: API Gateway extracts the header and passes it inrequest.AuthorizationToken.Request: Full request context is passedTokenResultTtlInSeconds0disables caching. Max36000Updated Attributes
[HttpApi]- AddedAuthorizerproperty to reference an authorizer by name[RestApi]- AddedAuthorizerproperty to reference an authorizer by nameBasic Usage
HTTP API Authorizer Example
REST API Authorizer Example
Authorizer with Custom Header and Caching
What Gets Generated
The source generator automatically creates all necessary CloudFormation resources:
AWS::ApiGatewayV2::AuthorizerorAWS::ApiGateway::AuthorizerresourcesAWS::Lambda::Permissionresources for API Gateway to invoke authorizersAuth.Authorizerconfiguration on protected routes