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.
| "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.
Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Writers/CloudFormationWriter.cs
Outdated
Show resolved
Hide resolved
| // 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); |
Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Writers/CloudFormationWriter.cs
Show resolved
Hide resolved
| } | ||
|
|
||
| _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); | |
| } |
Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Writers/CloudFormationWriter.cs
Outdated
Show resolved
Hide resolved
aead1d4 to
663a5b0
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 34 out of 34 changed files in this pull request and generated 9 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Writers/CloudFormationWriter.cs
Outdated
Show resolved
Hide resolved
| var httpApiId = await _cloudFormationHelper.GetResourcePhysicalIdAsync(_stackName, "ServerlessHttpApi"); | ||
| var restApiId = await _cloudFormationHelper.GetResourcePhysicalIdAsync(_stackName, "AnnotationsRestApi"); | ||
| Console.WriteLine($"[IntegrationTest] ServerlessHttpApi: {httpApiId}, AnnotationsRestApi: {restApiId}"); | ||
| HttpApiUrlPrefix = $"https://{httpApiId}.execute-api.{region}.amazonaws.com"; | ||
| RestApiUrlPrefix = $"https://{restApiId}.execute-api.{region}.amazonaws.com/Prod"; |
There was a problem hiding this comment.
GetResourcePhysicalIdAsync(_stackName, "AnnotationsRestApi") will always return null for TestServerlessApp because the generated template doesn't contain an AnnotationsRestApi logical resource (it uses SAM's implicit ServerlessRestApi when you have Type: Api events). This makes RestApiUrlPrefix invalid. Use the correct logical ID (likely ServerlessRestApi), or explicitly define the REST API resource in the template if that's the new intent.
Libraries/test/TestServerlessApp.IntegrationTests/IntegrationTestContextFixture.cs
Outdated
Show resolved
Hide resolved
| LambdaFunctions = await LambdaHelper.FilterByCloudFormationStackAsync(_stackName); | ||
| Console.WriteLine($"[IntegrationTest] Found {LambdaFunctions.Count} Lambda functions: {string.Join(", ", LambdaFunctions.Select(f => f.Name ?? "(null)"))}"); | ||
|
|
There was a problem hiding this comment.
LambdaFunctions can contain entries with Name == null (you already handle that in the log message). Later in initialization this list is used to wait for functions to become active; ensure null names are filtered out before invoking Lambda APIs, otherwise GetFunctionConfigurationAsync will throw when passed a null function name.
...st/Amazon.Lambda.Annotations.SourceGenerators.Tests/WriterTests/CloudFormationWriterTests.cs
Show resolved
Hide resolved
| dotnet restore | ||
| Write-Host "Creating CloudFormation Stack $identifier, Architecture $arch, Runtime $runtime" | ||
| dotnet lambda deploy-serverless --template-parameters "ArchitectureTypeParameter=$arch" |
There was a problem hiding this comment.
dotnet lambda deploy-serverless --template-parameters "ArchitectureTypeParameter=$arch" still passes ArchitectureTypeParameter, but TestServerlessApp/serverless.template no longer defines a Parameters.ArchitectureTypeParameter. CloudFormation deployments will fail with an unknown parameter. Either reintroduce the parameter in the template or remove the --template-parameters argument (and rely on whatever mechanism is now used to set architecture).
| private static string ConvertSqsUrlToArn(string queueUrl) | ||
| { | ||
| // SQS URL format: https://sqs.{region}.amazonaws.com/{account-id}/{queue-name} | ||
| var uri = new Uri(queueUrl); | ||
| var host = uri.Host; // sqs.us-west-2.amazonaws.com | ||
| var segments = uri.AbsolutePath.Trim('/').Split('/'); // [account-id, queue-name] | ||
| var region = host.Split('.')[1]; // us-west-2 | ||
| var accountId = segments[0]; | ||
| var queueName = segments[1]; | ||
| return $"arn:aws:sqs:{region}:{accountId}:{queueName}"; |
There was a problem hiding this comment.
ConvertSqsUrlToArn assumes queueUrl is non-null and has exactly two path segments after trimming. If GetResourcePhysicalIdAsync returns null or an unexpected URL format, this will throw (e.g., new Uri(queueUrl) or segments[1]). Add explicit null/format checks and a clearer failure message (or assert the resource ID is not null before calling this helper).
| var httpApiId = await _cloudFormationHelper.GetResourcePhysicalIdAsync(_stackName, "AnnotationsHttpApi"); | ||
| var implicitHttpApiId = await _cloudFormationHelper.GetResourcePhysicalIdAsync(_stackName, "ServerlessHttpApi"); | ||
| var restApiId = await _cloudFormationHelper.GetResourcePhysicalIdAsync(_stackName, "AnnotationsRestApi"); | ||
| Console.WriteLine($"[IntegrationTest] AnnotationsHttpApi: {httpApiId}, ServerlessHttpApi: {implicitHttpApiId}, AnnotationsRestApi: {restApiId}"); |
There was a problem hiding this comment.
GetResourcePhysicalIdAsync can return null when the logical resource ID isn't present or the stack query fails. Here, httpApiId/implicitHttpApiId/restApiId are interpolated directly into URLs, which can produce invalid URLs like https://.execute-api... and make failures harder to diagnose. Add explicit asserts (or throw) when any of these IDs are null/empty before constructing the base URLs.
| Console.WriteLine($"[IntegrationTest] AnnotationsHttpApi: {httpApiId}, ServerlessHttpApi: {implicitHttpApiId}, AnnotationsRestApi: {restApiId}"); | |
| Console.WriteLine($"[IntegrationTest] AnnotationsHttpApi: {httpApiId}, ServerlessHttpApi: {implicitHttpApiId}, AnnotationsRestApi: {restApiId}"); | |
| Assert.False(string.IsNullOrEmpty(httpApiId), $"CloudFormation resource 'AnnotationsHttpApi' was not found or has an empty physical ID for stack '{_stackName}'."); | |
| Assert.False(string.IsNullOrEmpty(implicitHttpApiId), $"CloudFormation resource 'ServerlessHttpApi' was not found or has an empty physical ID for stack '{_stackName}'."); | |
| Assert.False(string.IsNullOrEmpty(restApiId), $"CloudFormation resource 'AnnotationsRestApi' was not found or has an empty physical ID for stack '{_stackName}'."); |
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