diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs
index a606e5e88..69ecec8eb 100644
--- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs
+++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Diagnostics/DiagnosticDescriptors.cs
@@ -153,5 +153,70 @@ public static class DiagnosticDescriptors
category: "AWSLambdaCSharpGenerator",
DiagnosticSeverity.Error,
isEnabledByDefault: true);
+
+ // Authorizer diagnostics (ALA0019-ALA0027 per design document)
+ public static readonly DiagnosticDescriptor AuthorizerMissingName = new DiagnosticDescriptor(
+ id: "AWSLambda0120",
+ title: "Authorizer Name Required",
+ messageFormat: "The Name property is required on [{0}] attribute.",
+ category: "AWSLambdaCSharpGenerator",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ public static readonly DiagnosticDescriptor HttpApiAuthorizerNotFound = new DiagnosticDescriptor(
+ id: "AWSLambda0121",
+ title: "HTTP API Authorizer Not Found",
+ messageFormat: "Authorizer '{0}' referenced in [HttpApi] attribute does not exist. Add [HttpApiAuthorizer(Name = \"{0}\")] to an authorizer function.",
+ category: "AWSLambdaCSharpGenerator",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ public static readonly DiagnosticDescriptor RestApiAuthorizerNotFound = new DiagnosticDescriptor(
+ id: "AWSLambda0122",
+ title: "REST API Authorizer Not Found",
+ messageFormat: "Authorizer '{0}' referenced in [RestApi] attribute does not exist. Add [RestApiAuthorizer(Name = \"{0}\")] to an authorizer function.",
+ category: "AWSLambdaCSharpGenerator",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ public static readonly DiagnosticDescriptor HttpApiAuthorizerTypeMismatch = new DiagnosticDescriptor(
+ id: "AWSLambda0123",
+ title: "Authorizer Type Mismatch",
+ messageFormat: "Cannot use REST API authorizer '{0}' with [HttpApi] attribute. Use an [HttpApiAuthorizer] instead.",
+ category: "AWSLambdaCSharpGenerator",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ public static readonly DiagnosticDescriptor RestApiAuthorizerTypeMismatch = new DiagnosticDescriptor(
+ id: "AWSLambda0124",
+ title: "Authorizer Type Mismatch",
+ messageFormat: "Cannot use HTTP API authorizer '{0}' with [RestApi] attribute. Use a [RestApiAuthorizer] instead.",
+ category: "AWSLambdaCSharpGenerator",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ public static readonly DiagnosticDescriptor DuplicateAuthorizerName = new DiagnosticDescriptor(
+ id: "AWSLambda0125",
+ title: "Duplicate Authorizer Name",
+ messageFormat: "Duplicate authorizer name '{0}'. Authorizer names must be unique within the same API type.",
+ category: "AWSLambdaCSharpGenerator",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ public static readonly DiagnosticDescriptor InvalidAuthorizerPayloadFormatVersion = new DiagnosticDescriptor(
+ id: "AWSLambda0126",
+ title: "Invalid Payload Format Version",
+ messageFormat: "Invalid PayloadFormatVersion '{0}'. Must be \"1.0\" or \"2.0\".",
+ category: "AWSLambdaCSharpGenerator",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
+
+ public static readonly DiagnosticDescriptor InvalidAuthorizerResultTtl = new DiagnosticDescriptor(
+ id: "AWSLambda0127",
+ title: "Invalid Result TTL",
+ messageFormat: "Invalid ResultTtlInSeconds '{0}'. Must be between 0 and 3600.",
+ category: "AWSLambdaCSharpGenerator",
+ DiagnosticSeverity.Error,
+ isEnabledByDefault: true);
}
}
diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Generator.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Generator.cs
index 3001b6144..097dd5278 100644
--- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Generator.cs
+++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Generator.cs
@@ -1,7 +1,9 @@
+using Amazon.Lambda.Annotations.APIGateway;
using Amazon.Lambda.Annotations.SourceGenerator.Diagnostics;
using Amazon.Lambda.Annotations.SourceGenerator.Extensions;
using Amazon.Lambda.Annotations.SourceGenerator.FileIO;
using Amazon.Lambda.Annotations.SourceGenerator.Models;
+using Amazon.Lambda.Annotations.SourceGenerator.Models.Attributes;
using Amazon.Lambda.Annotations.SourceGenerator.Templates;
using Amazon.Lambda.Annotations.SourceGenerator.Writers;
using Microsoft.CodeAnalysis;
@@ -168,6 +170,13 @@ public void Execute(GeneratorExecutionContext context)
continue;
}
+ // Check for authorizer attributes on this Lambda function
+ var authorizerModel = ExtractAuthorizerModel(lambdaMethodSymbol, lambdaFunctionModel.ResourceName);
+ if (authorizerModel != null)
+ {
+ annotationReport.Authorizers.Add(authorizerModel);
+ }
+
var template = new LambdaFunctionTemplate(lambdaFunctionModel);
string sourceText;
@@ -296,5 +305,164 @@ public void Initialize(GeneratorInitializationContext context)
// Register a syntax receiver that will be created for each generation pass
context.RegisterForSyntaxNotifications(() => new SyntaxReceiver(_fileManager, _directoryManager));
}
+
+ ///
+ /// Extracts authorizer model from method symbol if it has HttpApiAuthorizer or RestApiAuthorizer attribute.
+ ///
+ /// The method symbol to check for authorizer attributes
+ /// The CloudFormation resource name for the Lambda function
+ /// AuthorizerModel if an authorizer attribute is found, null otherwise
+ private static AuthorizerModel ExtractAuthorizerModel(IMethodSymbol methodSymbol, string lambdaResourceName)
+ {
+ foreach (var attribute in methodSymbol.GetAttributes())
+ {
+ var attributeFullName = attribute.AttributeClass?.ToDisplayString();
+
+ if (attributeFullName == TypeFullNames.HttpApiAuthorizerAttribute)
+ {
+ return HttpApiAuthorizerAttributeBuilder.BuildModel(attribute, lambdaResourceName);
+ }
+
+ if (attributeFullName == TypeFullNames.RestApiAuthorizerAttribute)
+ {
+ return RestApiAuthorizerAttributeBuilder.BuildModel(attribute, lambdaResourceName);
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ /// Validates an authorizer model.
+ ///
+ /// The authorizer model to validate
+ /// The name of the attribute for error messages
+ /// The location of the method for diagnostic reporting
+ /// The diagnostic reporter for validation errors
+ /// True if valid, false otherwise
+ private static bool ValidateAuthorizerModel(AuthorizerModel model, string attributeName, Location methodLocation, DiagnosticReporter diagnosticReporter)
+ {
+ var isValid = true;
+
+ // Validate Name is provided
+ if (string.IsNullOrEmpty(model.Name))
+ {
+ diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.AuthorizerMissingName, methodLocation, attributeName));
+ isValid = false;
+ }
+
+ // Validate PayloadFormatVersion for HTTP API authorizers
+ if (model.AuthorizerType == AuthorizerType.HttpApi)
+ {
+ if (model.PayloadFormatVersion != "1.0" && model.PayloadFormatVersion != "2.0")
+ {
+ diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.InvalidAuthorizerPayloadFormatVersion, methodLocation, model.PayloadFormatVersion));
+ isValid = false;
+ }
+ }
+
+ // Validate ResultTtlInSeconds
+ if (model.ResultTtlInSeconds < 0 || model.ResultTtlInSeconds > 3600)
+ {
+ diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.InvalidAuthorizerResultTtl, methodLocation, model.ResultTtlInSeconds.ToString()));
+ isValid = false;
+ }
+
+ return isValid;
+ }
+
+ ///
+ /// Validates authorizer references in lambda functions.
+ ///
+ /// The annotation report containing all functions and authorizers
+ /// The diagnostic reporter for validation errors
+ /// True if all authorizer references are valid, false otherwise
+ private static bool ValidateAuthorizerReferences(AnnotationReport annotationReport, DiagnosticReporter diagnosticReporter)
+ {
+ var isValid = true;
+
+ // Build lookups for authorizers by type
+ var httpApiAuthorizers = annotationReport.Authorizers
+ .Where(a => a.AuthorizerType == AuthorizerType.HttpApi)
+ .ToDictionary(a => a.Name, a => a);
+ var restApiAuthorizers = annotationReport.Authorizers
+ .Where(a => a.AuthorizerType == AuthorizerType.RestApi)
+ .ToDictionary(a => a.Name, a => a);
+
+ // Check for duplicate authorizer names within the same API type
+ var httpApiAuthorizerNames = annotationReport.Authorizers
+ .Where(a => a.AuthorizerType == AuthorizerType.HttpApi)
+ .GroupBy(a => a.Name)
+ .Where(g => g.Count() > 1)
+ .Select(g => g.Key);
+
+ foreach (var duplicateName in httpApiAuthorizerNames)
+ {
+ diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.DuplicateAuthorizerName, Location.None, duplicateName));
+ isValid = false;
+ }
+
+ var restApiAuthorizerNames = annotationReport.Authorizers
+ .Where(a => a.AuthorizerType == AuthorizerType.RestApi)
+ .GroupBy(a => a.Name)
+ .Where(g => g.Count() > 1)
+ .Select(g => g.Key);
+
+ foreach (var duplicateName in restApiAuthorizerNames)
+ {
+ diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.DuplicateAuthorizerName, Location.None, duplicateName));
+ isValid = false;
+ }
+
+ // Validate authorizer references in functions
+ foreach (var function in annotationReport.LambdaFunctions)
+ {
+ var authorizerName = function.Authorizer;
+ if (string.IsNullOrEmpty(authorizerName))
+ {
+ continue;
+ }
+
+ // Check if this function uses HttpApi or RestApi
+ var usesHttpApi = function.Attributes.Any(a => a is AttributeModel);
+ var usesRestApi = function.Attributes.Any(a => a is AttributeModel);
+
+ if (usesHttpApi)
+ {
+ if (!httpApiAuthorizers.ContainsKey(authorizerName))
+ {
+ // Check if it exists as a REST API authorizer (type mismatch)
+ if (restApiAuthorizers.ContainsKey(authorizerName))
+ {
+ diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.HttpApiAuthorizerTypeMismatch, Location.None, authorizerName));
+ }
+ else
+ {
+ diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.HttpApiAuthorizerNotFound, Location.None, authorizerName));
+ }
+ isValid = false;
+ }
+ }
+
+ if (usesRestApi)
+ {
+ if (!restApiAuthorizers.ContainsKey(authorizerName))
+ {
+ // Check if it exists as an HTTP API authorizer (type mismatch)
+ if (httpApiAuthorizers.ContainsKey(authorizerName))
+ {
+ diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.RestApiAuthorizerTypeMismatch, Location.None, authorizerName));
+ }
+ else
+ {
+ diagnosticReporter.Report(Diagnostic.Create(DiagnosticDescriptors.RestApiAuthorizerNotFound, Location.None, authorizerName));
+ }
+ isValid = false;
+ }
+ }
+ }
+
+ return isValid;
+ }
}
}
diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/AnnotationReport.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/AnnotationReport.cs
index dbd76b458..419817a14 100644
--- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/AnnotationReport.cs
+++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/AnnotationReport.cs
@@ -9,6 +9,11 @@ public class AnnotationReport
///
public IList LambdaFunctions { get; } = new List();
+ ///
+ /// Collection of Lambda authorizers detected in the project
+ ///
+ public IList Authorizers { get; } = new List();
+
///
/// Path to the CloudFormation template for the Lambda project
///
diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/HttpApiAttributeBuilder.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/HttpApiAttributeBuilder.cs
index 15a6767bf..d21abd928 100644
--- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/HttpApiAttributeBuilder.cs
+++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/HttpApiAttributeBuilder.cs
@@ -17,12 +17,14 @@ public static HttpApiAttribute Build(AttributeData att)
var method = (LambdaHttpMethod)att.ConstructorArguments[0].Value;
var template = att.ConstructorArguments[1].Value as string;
var version = att.NamedArguments.FirstOrDefault(arg => arg.Key == "Version").Value.Value;
+ var authorizer = att.NamedArguments.FirstOrDefault(arg => arg.Key == "Authorizer").Value.Value as string;
var data = new HttpApiAttribute(method, template)
{
- Version = version == null ? HttpApiVersion.V2 : (HttpApiVersion)version
+ Version = version == null ? HttpApiVersion.V2 : (HttpApiVersion)version,
+ Authorizer = authorizer
};
return data;
}
}
-}
\ No newline at end of file
+}
diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/HttpApiAuthorizerAttributeBuilder.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/HttpApiAuthorizerAttributeBuilder.cs
new file mode 100644
index 000000000..8df840490
--- /dev/null
+++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/HttpApiAuthorizerAttributeBuilder.cs
@@ -0,0 +1,78 @@
+using System.Linq;
+using Amazon.Lambda.Annotations.APIGateway;
+using Microsoft.CodeAnalysis;
+
+namespace Amazon.Lambda.Annotations.SourceGenerator.Models.Attributes
+{
+ ///
+ /// Builder for .
+ ///
+ public static class HttpApiAuthorizerAttributeBuilder
+ {
+ ///
+ /// Builds an from the Roslyn attribute data.
+ ///
+ /// The attribute data from Roslyn
+ /// The populated attribute instance
+ public static HttpApiAuthorizerAttribute Build(AttributeData att)
+ {
+ var attribute = new HttpApiAuthorizerAttribute();
+
+ foreach (var namedArg in att.NamedArguments)
+ {
+ switch (namedArg.Key)
+ {
+ case nameof(HttpApiAuthorizerAttribute.Name):
+ attribute.Name = namedArg.Value.Value as string;
+ break;
+ case nameof(HttpApiAuthorizerAttribute.IdentityHeader):
+ attribute.IdentityHeader = namedArg.Value.Value as string ?? "Authorization";
+ break;
+ case nameof(HttpApiAuthorizerAttribute.EnableSimpleResponses):
+ attribute.EnableSimpleResponses = namedArg.Value.Value is bool val ? val : true;
+ break;
+ case nameof(HttpApiAuthorizerAttribute.PayloadFormatVersion):
+ attribute.PayloadFormatVersion = namedArg.Value.Value as string ?? "2.0";
+ break;
+ case nameof(HttpApiAuthorizerAttribute.ResultTtlInSeconds):
+ attribute.ResultTtlInSeconds = namedArg.Value.Value is int ttl ? ttl : 0;
+ break;
+ }
+ }
+
+ return attribute;
+ }
+
+ ///
+ /// Builds an from the attribute and lambda function resource name.
+ ///
+ /// The attribute data from Roslyn
+ /// The CloudFormation resource name for the Lambda function
+ /// The populated authorizer model
+ public static AuthorizerModel BuildModel(AttributeData att, string lambdaResourceName)
+ {
+ var attribute = Build(att);
+ return BuildModel(attribute, lambdaResourceName);
+ }
+
+ ///
+ /// Builds an from the attribute and lambda function resource name.
+ ///
+ /// The parsed attribute
+ /// The CloudFormation resource name for the Lambda function
+ /// The populated authorizer model
+ public static AuthorizerModel BuildModel(HttpApiAuthorizerAttribute attribute, string lambdaResourceName)
+ {
+ return new AuthorizerModel
+ {
+ Name = attribute.Name,
+ LambdaResourceName = lambdaResourceName,
+ AuthorizerType = AuthorizerType.HttpApi,
+ IdentityHeader = attribute.IdentityHeader,
+ ResultTtlInSeconds = attribute.ResultTtlInSeconds,
+ EnableSimpleResponses = attribute.EnableSimpleResponses,
+ PayloadFormatVersion = attribute.PayloadFormatVersion
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/RestApiAttributeBuilder.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/RestApiAttributeBuilder.cs
index 1a9f44680..44ab96a10 100644
--- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/RestApiAttributeBuilder.cs
+++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/RestApiAttributeBuilder.cs
@@ -1,4 +1,5 @@
using System;
+using System.Linq;
using Amazon.Lambda.Annotations.APIGateway;
using Microsoft.CodeAnalysis;
@@ -18,10 +19,14 @@ public static RestApiAttribute Build(AttributeData att)
var method = (LambdaHttpMethod)att.ConstructorArguments[0].Value;
var template = att.ConstructorArguments[1].Value as string;
+ var authorizer = att.NamedArguments.FirstOrDefault(arg => arg.Key == "Authorizer").Value.Value as string;
- var data = new RestApiAttribute(method, template);
+ var data = new RestApiAttribute(method, template)
+ {
+ Authorizer = authorizer
+ };
return data;
}
}
-}
\ No newline at end of file
+}
diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/RestApiAuthorizerAttributeBuilder.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/RestApiAuthorizerAttributeBuilder.cs
new file mode 100644
index 000000000..b981c6ed0
--- /dev/null
+++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/Attributes/RestApiAuthorizerAttributeBuilder.cs
@@ -0,0 +1,76 @@
+using System.Linq;
+using Amazon.Lambda.Annotations.APIGateway;
+using Microsoft.CodeAnalysis;
+
+namespace Amazon.Lambda.Annotations.SourceGenerator.Models.Attributes
+{
+ ///
+ /// Builder for .
+ ///
+ public static class RestApiAuthorizerAttributeBuilder
+ {
+ ///
+ /// Builds a from the Roslyn attribute data.
+ ///
+ /// The attribute data from Roslyn
+ /// The populated attribute instance
+ public static RestApiAuthorizerAttribute Build(AttributeData att)
+ {
+ var attribute = new RestApiAuthorizerAttribute();
+
+ foreach (var namedArg in att.NamedArguments)
+ {
+ switch (namedArg.Key)
+ {
+ case nameof(RestApiAuthorizerAttribute.Name):
+ attribute.Name = namedArg.Value.Value as string;
+ break;
+ case nameof(RestApiAuthorizerAttribute.IdentityHeader):
+ attribute.IdentityHeader = namedArg.Value.Value as string ?? "Authorization";
+ break;
+ case nameof(RestApiAuthorizerAttribute.Type):
+ attribute.Type = namedArg.Value.Value is int typeVal
+ ? (RestApiAuthorizerType)typeVal
+ : RestApiAuthorizerType.Token;
+ break;
+ case nameof(RestApiAuthorizerAttribute.ResultTtlInSeconds):
+ attribute.ResultTtlInSeconds = namedArg.Value.Value is int ttl ? ttl : 0;
+ break;
+ }
+ }
+
+ return attribute;
+ }
+
+ ///
+ /// Builds an from the attribute and lambda function resource name.
+ ///
+ /// The attribute data from Roslyn
+ /// The CloudFormation resource name for the Lambda function
+ /// The populated authorizer model
+ public static AuthorizerModel BuildModel(AttributeData att, string lambdaResourceName)
+ {
+ var attribute = Build(att);
+ return BuildModel(attribute, lambdaResourceName);
+ }
+
+ ///
+ /// Builds an from the attribute and lambda function resource name.
+ ///
+ /// The parsed attribute
+ /// The CloudFormation resource name for the Lambda function
+ /// The populated authorizer model
+ public static AuthorizerModel BuildModel(RestApiAuthorizerAttribute attribute, string lambdaResourceName)
+ {
+ return new AuthorizerModel
+ {
+ Name = attribute.Name,
+ LambdaResourceName = lambdaResourceName,
+ AuthorizerType = AuthorizerType.RestApi,
+ IdentityHeader = attribute.IdentityHeader,
+ ResultTtlInSeconds = attribute.ResultTtlInSeconds,
+ RestApiAuthorizerType = attribute.Type
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/AuthorizerModel.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/AuthorizerModel.cs
new file mode 100644
index 000000000..645279b21
--- /dev/null
+++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/AuthorizerModel.cs
@@ -0,0 +1,99 @@
+using Amazon.Lambda.Annotations.APIGateway;
+
+namespace Amazon.Lambda.Annotations.SourceGenerator.Models
+{
+ ///
+ /// Enumeration for the type of API Gateway authorizer
+ ///
+ public enum AuthorizerType
+ {
+ ///
+ /// HTTP API (API Gateway V2) authorizer
+ ///
+ HttpApi,
+
+ ///
+ /// REST API (API Gateway V1) authorizer
+ ///
+ RestApi
+ }
+
+ ///
+ /// Model representing a Lambda Authorizer configuration
+ ///
+ public class AuthorizerModel
+ {
+ ///
+ /// Unique name to identify this authorizer. Functions reference this name.
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// The CloudFormation resource name for the Lambda function that implements this authorizer.
+ /// This is derived from the LambdaFunctionAttribute's ResourceName or the generated method name.
+ ///
+ public string LambdaResourceName { get; set; }
+
+ ///
+ /// The type of API Gateway authorizer (HTTP API or REST API)
+ ///
+ public AuthorizerType AuthorizerType { get; set; }
+
+ ///
+ /// Header name to use as identity source.
+ ///
+ public string IdentityHeader { get; set; }
+
+ ///
+ /// TTL in seconds for caching authorizer results.
+ ///
+ public int ResultTtlInSeconds { get; set; }
+
+ // HTTP API specific properties
+
+ ///
+ /// Whether to use simple responses (IsAuthorized: true/false) or IAM policy responses.
+ /// Only applicable for HTTP API authorizers.
+ ///
+ public bool EnableSimpleResponses { get; set; }
+
+ ///
+ /// Authorizer payload format version. Valid values: "1.0" or "2.0".
+ /// Only applicable for HTTP API authorizers.
+ ///
+ public string PayloadFormatVersion { get; set; }
+
+ // REST API specific properties
+
+ ///
+ /// Type of REST API authorizer: Token or Request.
+ /// Only applicable for REST API authorizers.
+ ///
+ public RestApiAuthorizerType RestApiAuthorizerType { get; set; }
+
+ ///
+ /// Gets the identity source string formatted for CloudFormation.
+ ///
+ /// The formatted identity source string
+ public string GetIdentitySource()
+ {
+ if (AuthorizerType == AuthorizerType.HttpApi)
+ {
+ return $"$request.header.{IdentityHeader}";
+ }
+ else
+ {
+ return $"method.request.header.{IdentityHeader}";
+ }
+ }
+
+ ///
+ /// Gets the CloudFormation resource name for this authorizer.
+ ///
+ /// The CloudFormation resource name
+ public string GetAuthorizerResourceName()
+ {
+ return $"{Name}Authorizer";
+ }
+ }
+}
\ No newline at end of file
diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/ILambdaFunctionSerializable.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/ILambdaFunctionSerializable.cs
index 6cb6161fa..d0b7fd20f 100644
--- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/ILambdaFunctionSerializable.cs
+++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/ILambdaFunctionSerializable.cs
@@ -77,5 +77,11 @@ public interface ILambdaFunctionSerializable
/// The assembly version of the Amazon.Lambda.Annotations.SourceGenerator package.
///
string SourceGeneratorVersion { get; set; }
+
+ ///
+ /// The name of the authorizer protecting this Lambda function endpoint.
+ /// Null or empty for public (unauthenticated) endpoints.
+ ///
+ string Authorizer { get; }
}
-}
\ No newline at end of file
+}
diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/LambdaFunctionModel.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/LambdaFunctionModel.cs
index fad1f5f53..fd47d2dab 100644
--- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/LambdaFunctionModel.cs
+++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/LambdaFunctionModel.cs
@@ -78,9 +78,12 @@ public class LambdaFunctionModel : ILambdaFunctionSerializable
///
public string SourceGeneratorVersion { get; set; }
+ ///
+ public string Authorizer { get; set; }
+
///
/// Indicates if the model is valid.
///
public bool IsValid { get; set; }
}
-}
\ No newline at end of file
+}
diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/LambdaFunctionModelBuilder.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/LambdaFunctionModelBuilder.cs
index e828bee3d..db7fa4d67 100644
--- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/LambdaFunctionModelBuilder.cs
+++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Models/LambdaFunctionModelBuilder.cs
@@ -1,7 +1,9 @@
using System;
using System.Linq;
+using Amazon.Lambda.Annotations.APIGateway;
using Amazon.Lambda.Annotations.SourceGenerator.Diagnostics;
using Amazon.Lambda.Annotations.SourceGenerator.Extensions;
+using Amazon.Lambda.Annotations.SourceGenerator.Models.Attributes;
using Amazon.Lambda.Annotations.SourceGenerator.Validation;
using Microsoft.CodeAnalysis;
@@ -40,11 +42,31 @@ private static LambdaFunctionModel Build(IMethodSymbol lambdaMethodSymbol, IMeth
?.Version.ToString(),
IsExecutable = isExecutable,
Runtime = runtime,
+ Authorizer = GetAuthorizerFromAttributes(lambdaMethod)
};
return model;
}
+ ///
+ /// Extracts the Authorizer name from HttpApi or RestApi attributes.
+ ///
+ private static string GetAuthorizerFromAttributes(LambdaMethodModel lambdaMethod)
+ {
+ foreach (var attribute in lambdaMethod.Attributes)
+ {
+ if (attribute is AttributeModel httpApiAttributeModel)
+ {
+ return httpApiAttributeModel.Data.Authorizer;
+ }
+ if (attribute is AttributeModel restApiAttributeModel)
+ {
+ return restApiAttributeModel.Data.Authorizer;
+ }
+ }
+ return null;
+ }
+
private static LambdaSerializerInfo GetSerializerInfoAttribute(GeneratorExecutionContext context, IMethodSymbol methodModel)
{
var serializerString = TypeFullNames.DefaultLambdaSerializer;
diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/TypeFullNames.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/TypeFullNames.cs
index a91cfaefa..56582a2e7 100644
--- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/TypeFullNames.cs
+++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/TypeFullNames.cs
@@ -33,6 +33,9 @@ public static class TypeFullNames
public const string FromRouteAttribute = "Amazon.Lambda.Annotations.APIGateway.FromRouteAttribute";
public const string FromCustomAuthorizerAttribute = "Amazon.Lambda.Annotations.APIGateway.FromCustomAuthorizerAttribute";
+ public const string HttpApiAuthorizerAttribute = "Amazon.Lambda.Annotations.APIGateway.HttpApiAuthorizerAttribute";
+ public const string RestApiAuthorizerAttribute = "Amazon.Lambda.Annotations.APIGateway.RestApiAuthorizerAttribute";
+
public const string SQSEvent = "Amazon.Lambda.SQSEvents.SQSEvent";
public const string SQSBatchResponse = "Amazon.Lambda.SQSEvents.SQSBatchResponse";
public const string SQSEventAttribute = "Amazon.Lambda.Annotations.SQS.SQSEventAttribute";
diff --git a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Writers/CloudFormationWriter.cs b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Writers/CloudFormationWriter.cs
index 993a312ed..a64836e11 100644
--- a/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Writers/CloudFormationWriter.cs
+++ b/Libraries/src/Amazon.Lambda.Annotations.SourceGenerator/Writers/CloudFormationWriter.cs
@@ -10,6 +10,7 @@
using System.ComponentModel;
using System.Linq;
using System.Reflection;
+using AuthorizerType = Amazon.Lambda.Annotations.SourceGenerator.Models.AuthorizerType;
namespace Amazon.Lambda.Annotations.SourceGenerator.Writers
{
@@ -25,6 +26,8 @@ public class CloudFormationWriter : IAnnotationReportWriter
private const string PARAMETERS = "Parameters";
private const string GET_ATTRIBUTE = "Fn::GetAtt";
private const string REF = "Ref";
+ private const string HTTP_API_RESOURCE_NAME = "CustomHttpApi";
+ private const string REST_API_RESOURCE_NAME = "CustomRestApi";
// Constants related to the message we append to the CloudFormation template description
private const string BASE_DESCRIPTION = "This template is partially managed by Amazon.Lambda.Annotations";
@@ -59,17 +62,24 @@ public void ApplyReport(AnnotationReport report)
ProcessTemplateDescription(report);
+ // Build authorizer lookup for processing events with Auth configuration
+ var authorizerLookup = report.Authorizers.ToDictionary(a => a.Name, a => a);
+
+ // Process authorizers first (they need to exist before functions reference them)
+ ProcessAuthorizers(report.Authorizers);
+
var processedLambdaFunctions = new HashSet();
foreach (var lambdaFunction in report.LambdaFunctions)
{
if (!ShouldProcessLambdaFunction(lambdaFunction))
continue;
- ProcessLambdaFunction(lambdaFunction, relativeProjectUri);
+ ProcessLambdaFunction(lambdaFunction, relativeProjectUri, authorizerLookup);
processedLambdaFunctions.Add(lambdaFunction.ResourceName);
}
RemoveOrphanedLambdaFunctions(processedLambdaFunctions);
+ RemoveOrphanedAuthorizers(report.Authorizers);
var content = _templateWriter.GetContent();
_fileManager.WriteAllText(report.CloudFormationTemplatePath, content);
@@ -98,7 +108,7 @@ private bool ShouldProcessLambdaFunction(ILambdaFunctionSerializable lambdaFunct
/// Captures different properties specified by and attributes specified by
/// and writes it to the serverless template.
///
- private void ProcessLambdaFunction(ILambdaFunctionSerializable lambdaFunction, string relativeProjectUri)
+ private void ProcessLambdaFunction(ILambdaFunctionSerializable lambdaFunction, string relativeProjectUri, Dictionary authorizerLookup)
{
var lambdaFunctionPath = $"Resources.{lambdaFunction.ResourceName}";
var propertiesPath = $"{lambdaFunctionPath}.Properties";
@@ -107,7 +117,7 @@ private void ProcessLambdaFunction(ILambdaFunctionSerializable lambdaFunction, s
ApplyLambdaFunctionDefaults(lambdaFunctionPath, propertiesPath, lambdaFunction.Runtime);
ProcessLambdaFunctionProperties(lambdaFunction, propertiesPath, relativeProjectUri);
- ProcessLambdaFunctionEventAttributes(lambdaFunction);
+ ProcessLambdaFunctionEventAttributes(lambdaFunction, authorizerLookup);
}
///
@@ -180,7 +190,7 @@ private void ProcessPackageTypeProperty(ILambdaFunctionSerializable lambdaFuncti
/// It also removes all events that exist in the serverless template but were not encountered during the current source generation pass.
/// All events are specified under 'Resources.FUNCTION_NAME.Properties.Events' path.
///
- private void ProcessLambdaFunctionEventAttributes(ILambdaFunctionSerializable lambdaFunction)
+ private void ProcessLambdaFunctionEventAttributes(ILambdaFunctionSerializable lambdaFunction, Dictionary authorizerLookup)
{
var currentSyncedEvents = new List();
var currentSyncedEventProperties = new Dictionary>();
@@ -191,11 +201,11 @@ private void ProcessLambdaFunctionEventAttributes(ILambdaFunctionSerializable la
switch (attributeModel)
{
case AttributeModel httpApiAttributeModel:
- eventName = ProcessHttpApiAttribute(lambdaFunction, httpApiAttributeModel.Data, currentSyncedEventProperties);
+ eventName = ProcessHttpApiAttribute(lambdaFunction, httpApiAttributeModel.Data, currentSyncedEventProperties, authorizerLookup);
currentSyncedEvents.Add(eventName);
break;
case AttributeModel restApiAttributeModel:
- eventName = ProcessRestApiAttribute(lambdaFunction, restApiAttributeModel.Data, currentSyncedEventProperties);
+ eventName = ProcessRestApiAttribute(lambdaFunction, restApiAttributeModel.Data, currentSyncedEventProperties, authorizerLookup);
currentSyncedEvents.Add(eventName);
break;
case AttributeModel sqsAttributeModel:
@@ -211,7 +221,7 @@ private void ProcessLambdaFunctionEventAttributes(ILambdaFunctionSerializable la
///
/// Writes all properties associated with to the serverless template.
///
- private string ProcessRestApiAttribute(ILambdaFunctionSerializable lambdaFunction, RestApiAttribute restApiAttribute, Dictionary> syncedEventProperties)
+ private string ProcessRestApiAttribute(ILambdaFunctionSerializable lambdaFunction, RestApiAttribute restApiAttribute, Dictionary> syncedEventProperties, Dictionary authorizerLookup)
{
var eventName = $"Root{restApiAttribute.Method}";
var eventPath = $"Resources.{lambdaFunction.ResourceName}.Properties.Events.{eventName}";
@@ -220,13 +230,22 @@ private string ProcessRestApiAttribute(ILambdaFunctionSerializable lambdaFunctio
SetEventProperty(syncedEventProperties, lambdaFunction.ResourceName, eventName, "Path", restApiAttribute.Template);
SetEventProperty(syncedEventProperties, lambdaFunction.ResourceName, eventName, "Method", restApiAttribute.Method.ToString().ToUpper());
+ // Set Auth configuration if authorizer is specified
+ // Use the authorizer name directly (not a CloudFormation Ref) since authorizers are defined inline in the API
+ // Also set RestApiId to link to our explicit ServerlessRestApi resource where the authorizer is defined
+ if (!string.IsNullOrEmpty(restApiAttribute.Authorizer) && authorizerLookup.TryGetValue(restApiAttribute.Authorizer, out var authorizer))
+ {
+ SetEventProperty(syncedEventProperties, lambdaFunction.ResourceName, eventName, "Auth.Authorizer", authorizer.Name);
+ SetEventProperty(syncedEventProperties, lambdaFunction.ResourceName, eventName, $"RestApiId.{REF}", REST_API_RESOURCE_NAME);
+ }
+
return eventName;
}
///
/// Writes all properties associated with to the serverless template.
///
- private string ProcessHttpApiAttribute(ILambdaFunctionSerializable lambdaFunction, HttpApiAttribute httpApiAttribute, Dictionary> syncedEventProperties)
+ private string ProcessHttpApiAttribute(ILambdaFunctionSerializable lambdaFunction, HttpApiAttribute httpApiAttribute, Dictionary> syncedEventProperties, Dictionary authorizerLookup)
{
var eventName = $"Root{httpApiAttribute.Method}";
var eventPath = $"Resources.{lambdaFunction.ResourceName}.Properties.Events.{eventName}";
@@ -240,9 +259,221 @@ private string ProcessHttpApiAttribute(ILambdaFunctionSerializable lambdaFunctio
if (httpApiAttribute.Version == HttpApiVersion.V1)
SetEventProperty(syncedEventProperties, lambdaFunction.ResourceName, eventName, "PayloadFormatVersion", "1.0");
+ // Set Auth configuration if authorizer is specified
+ // Use the authorizer name directly (not a CloudFormation Ref) since authorizers are defined inline in the API
+ // Also set ApiId to link to our explicit ServerlessHttpApi resource where the authorizer is defined
+ if (!string.IsNullOrEmpty(httpApiAttribute.Authorizer) && authorizerLookup.TryGetValue(httpApiAttribute.Authorizer, out var authorizer))
+ {
+ SetEventProperty(syncedEventProperties, lambdaFunction.ResourceName, eventName, "Auth.Authorizer", authorizer.Name);
+ SetEventProperty(syncedEventProperties, lambdaFunction.ResourceName, eventName, $"ApiId.{REF}", HTTP_API_RESOURCE_NAME);
+ }
+
return eventName;
}
+ ///
+ /// Processes all authorizers and writes them to the serverless template as inline authorizers within the API resources.
+ /// AWS SAM expects authorizers to be defined within the Auth.Authorizers property of AWS::Serverless::HttpApi or AWS::Serverless::Api resources.
+ ///
+ private void ProcessAuthorizers(IList authorizers)
+ {
+ // Group authorizers by type
+ var httpApiAuthorizers = authorizers.Where(a => a.AuthorizerType == AuthorizerType.HttpApi).ToList();
+ var restApiAuthorizers = authorizers.Where(a => a.AuthorizerType == AuthorizerType.RestApi).ToList();
+
+ // Process HTTP API authorizers (add to CustomHttpApi resource)
+ if (httpApiAuthorizers.Any())
+ {
+ ProcessHttpApiAuthorizers(httpApiAuthorizers);
+ }
+
+ // Process REST API authorizers (add to CustomRestApi resource)
+ if (restApiAuthorizers.Any())
+ {
+ ProcessRestApiAuthorizers(restApiAuthorizers);
+ }
+ }
+
+ ///
+ /// Writes HTTP API (API Gateway V2) authorizers to the CustomHttpApi resource.
+ /// SAM expects authorizers to be defined inline in the Auth.Authorizers property.
+ ///
+ private void ProcessHttpApiAuthorizers(IList authorizers)
+ {
+ const string httpApiResourcePath = "Resources." + HTTP_API_RESOURCE_NAME;
+
+ // Create the CustomHttpApi resource if it doesn't exist
+ if (!_templateWriter.Exists(httpApiResourcePath))
+ {
+ _templateWriter.SetToken($"{httpApiResourcePath}.Type", "AWS::Serverless::HttpApi");
+ }
+
+ _templateWriter.SetToken($"{httpApiResourcePath}.Metadata.Tool", CREATION_TOOL);
+
+ // Add each authorizer to the Auth.Authorizers map
+ foreach (var authorizer in authorizers)
+ {
+ var authorizerPath = $"{httpApiResourcePath}.Properties.Auth.Authorizers.{authorizer.Name}";
+
+ // FunctionArn - Reference to the Lambda function ARN
+ _templateWriter.SetToken($"{authorizerPath}.FunctionArn.{GET_ATTRIBUTE}", new List { authorizer.LambdaResourceName, "Arn" }, TokenType.List);
+
+ // AuthorizerPayloadFormatVersion
+ _templateWriter.SetToken($"{authorizerPath}.AuthorizerPayloadFormatVersion", authorizer.PayloadFormatVersion);
+
+ // EnableSimpleResponses
+ _templateWriter.SetToken($"{authorizerPath}.EnableSimpleResponses", authorizer.EnableSimpleResponses);
+
+ // Identity.Headers - The header to use for identity source
+ _templateWriter.SetToken($"{authorizerPath}.Identity.Headers", new List { authorizer.IdentityHeader }, TokenType.List);
+
+ // AuthorizerResultTtlInSeconds (only if caching is enabled)
+ if (authorizer.ResultTtlInSeconds > 0)
+ {
+ _templateWriter.SetToken($"{authorizerPath}.FunctionInvokeRole", null); // Required for caching
+ }
+ }
+ }
+
+ ///
+ /// Writes REST API (API Gateway V1) authorizers to the CustomRestApi resource.
+ /// SAM expects authorizers to be defined inline in the Auth.Authorizers property.
+ ///
+ private void ProcessRestApiAuthorizers(IList authorizers)
+ {
+ const string restApiResourcePath = "Resources." + REST_API_RESOURCE_NAME;
+
+ // Create the CustomRestApi resource if it doesn't exist
+ if (!_templateWriter.Exists(restApiResourcePath))
+ {
+ _templateWriter.SetToken($"{restApiResourcePath}.Type", "AWS::Serverless::Api");
+ // REST API requires explicit stage name
+ _templateWriter.SetToken($"{restApiResourcePath}.Properties.StageName", "Prod");
+ }
+
+ _templateWriter.SetToken($"{restApiResourcePath}.Metadata.Tool", CREATION_TOOL);
+
+ // Add each authorizer to the Auth.Authorizers map
+ foreach (var authorizer in authorizers)
+ {
+ var authorizerPath = $"{restApiResourcePath}.Properties.Auth.Authorizers.{authorizer.Name}";
+
+ // FunctionArn - Reference to the Lambda function ARN using GetAtt
+ _templateWriter.SetToken($"{authorizerPath}.FunctionArn.{GET_ATTRIBUTE}", new List { authorizer.LambdaResourceName, "Arn" }, TokenType.List);
+
+ // Identity.Header - The header to use for identity source
+ _templateWriter.SetToken($"{authorizerPath}.Identity.Header", authorizer.IdentityHeader);
+
+ // FunctionPayloadType - TOKEN or REQUEST
+ if (authorizer.RestApiAuthorizerType == RestApiAuthorizerType.Token)
+ {
+ _templateWriter.SetToken($"{authorizerPath}.FunctionPayloadType", "TOKEN");
+ }
+ else
+ {
+ _templateWriter.SetToken($"{authorizerPath}.FunctionPayloadType", "REQUEST");
+ }
+ }
+ }
+
+ ///
+ /// Removes orphaned authorizers from the serverless template.
+ /// Authorizers are now defined inline within the API resources (CustomHttpApi and CustomRestApi).
+ /// This method removes authorizers that were created by Lambda Annotations but no longer exist in the current compilation.
+ /// It also cleans up legacy standalone authorizer resources (AWS::ApiGatewayV2::Authorizer, AWS::ApiGateway::Authorizer)
+ /// and their associated Lambda permissions.
+ ///
+ private void RemoveOrphanedAuthorizers(IList currentAuthorizers)
+ {
+ if (!_templateWriter.Exists("Resources"))
+ {
+ return;
+ }
+
+ // Get current authorizer names by type
+ var currentHttpApiAuthorizerNames = new HashSet(
+ currentAuthorizers.Where(a => a.AuthorizerType == AuthorizerType.HttpApi).Select(a => a.Name));
+ var currentRestApiAuthorizerNames = new HashSet(
+ currentAuthorizers.Where(a => a.AuthorizerType == AuthorizerType.RestApi).Select(a => a.Name));
+
+ // Clean up orphaned inline authorizers in CustomHttpApi
+ const string httpApiAuthorizersPath = "Resources." + HTTP_API_RESOURCE_NAME + ".Properties.Auth.Authorizers";
+ if (_templateWriter.Exists(httpApiAuthorizersPath))
+ {
+ var httpApiCreationTool = _templateWriter.GetToken($"Resources.{HTTP_API_RESOURCE_NAME}.Metadata.Tool", string.Empty);
+ if (string.Equals(httpApiCreationTool, CREATION_TOOL, StringComparison.Ordinal))
+ {
+ var existingAuthorizerNames = _templateWriter.GetKeys(httpApiAuthorizersPath);
+ foreach (var authorizerName in existingAuthorizerNames)
+ {
+ if (!currentHttpApiAuthorizerNames.Contains(authorizerName))
+ {
+ _templateWriter.RemoveToken($"{httpApiAuthorizersPath}.{authorizerName}");
+ }
+ }
+
+ // Clean up empty Auth structure
+ _templateWriter.RemoveTokenIfNullOrEmpty(httpApiAuthorizersPath);
+ _templateWriter.RemoveTokenIfNullOrEmpty($"Resources.{HTTP_API_RESOURCE_NAME}.Properties.Auth");
+ }
+ }
+
+ // Clean up orphaned inline authorizers in CustomRestApi
+ const string restApiAuthorizersPath = "Resources." + REST_API_RESOURCE_NAME + ".Properties.Auth.Authorizers";
+ if (_templateWriter.Exists(restApiAuthorizersPath))
+ {
+ var restApiCreationTool = _templateWriter.GetToken($"Resources.{REST_API_RESOURCE_NAME}.Metadata.Tool", string.Empty);
+ if (string.Equals(restApiCreationTool, CREATION_TOOL, StringComparison.Ordinal))
+ {
+ var existingAuthorizerNames = _templateWriter.GetKeys(restApiAuthorizersPath);
+ foreach (var authorizerName in existingAuthorizerNames)
+ {
+ if (!currentRestApiAuthorizerNames.Contains(authorizerName))
+ {
+ _templateWriter.RemoveToken($"{restApiAuthorizersPath}.{authorizerName}");
+ }
+ }
+
+ // Clean up empty Auth structure
+ _templateWriter.RemoveTokenIfNullOrEmpty(restApiAuthorizersPath);
+ _templateWriter.RemoveTokenIfNullOrEmpty($"Resources.{REST_API_RESOURCE_NAME}.Properties.Auth");
+ }
+ }
+
+ // Clean up legacy standalone authorizer resources and permissions from older versions
+ var toRemove = new List();
+ foreach (var resourceName in _templateWriter.GetKeys("Resources"))
+ {
+ var resourcePath = $"Resources.{resourceName}";
+ var type = _templateWriter.GetToken($"{resourcePath}.Type", string.Empty);
+ var creationTool = _templateWriter.GetToken($"{resourcePath}.Metadata.Tool", string.Empty);
+
+ if (!string.Equals(creationTool, CREATION_TOOL, StringComparison.Ordinal))
+ {
+ continue;
+ }
+
+ // Remove legacy standalone authorizer resources
+ if (string.Equals(type, "AWS::ApiGatewayV2::Authorizer", StringComparison.Ordinal) ||
+ string.Equals(type, "AWS::ApiGateway::Authorizer", StringComparison.Ordinal))
+ {
+ toRemove.Add(resourceName);
+ }
+
+ // Remove legacy authorizer Lambda permissions
+ if (string.Equals(type, "AWS::Lambda::Permission", StringComparison.Ordinal) &&
+ resourceName.EndsWith("AuthorizerPermission"))
+ {
+ toRemove.Add(resourceName);
+ }
+ }
+
+ foreach (var resourceName in toRemove)
+ {
+ _templateWriter.RemoveToken($"Resources.{resourceName}");
+ }
+ }
+
///
/// Writes all properties associated with to the serverless template.
///
diff --git a/Libraries/src/Amazon.Lambda.Annotations/APIGateway/HttpApiAttribute.cs b/Libraries/src/Amazon.Lambda.Annotations/APIGateway/HttpApiAttribute.cs
index eb4fc5a3a..954832cd6 100644
--- a/Libraries/src/Amazon.Lambda.Annotations/APIGateway/HttpApiAttribute.cs
+++ b/Libraries/src/Amazon.Lambda.Annotations/APIGateway/HttpApiAttribute.cs
@@ -22,6 +22,13 @@ public class HttpApiAttribute : Attribute
///
public LambdaHttpMethod Method { get; set; }
+ ///
+ /// Name of the HTTP API Lambda authorizer to protect this endpoint.
+ /// Must match the Name property of an in this project.
+ /// Leave null/empty for public (unauthenticated) endpoints.
+ ///
+ public string Authorizer { get; set; }
+
///
/// Constructs a
///
diff --git a/Libraries/src/Amazon.Lambda.Annotations/APIGateway/HttpApiAuthorizerAttribute.cs b/Libraries/src/Amazon.Lambda.Annotations/APIGateway/HttpApiAuthorizerAttribute.cs
new file mode 100644
index 000000000..e69e59b9a
--- /dev/null
+++ b/Libraries/src/Amazon.Lambda.Annotations/APIGateway/HttpApiAuthorizerAttribute.cs
@@ -0,0 +1,69 @@
+using System;
+
+namespace Amazon.Lambda.Annotations.APIGateway
+{
+ ///
+ /// Marks this Lambda function as an HTTP API (API Gateway V2) authorizer.
+ /// Other functions can reference this authorizer using the HttpApi attribute's Authorizer property.
+ ///
+ ///
+ /// This attribute must be used in conjunction with the .
+ /// The authorizer function should return
+ /// when is true, or
+ /// when is false.
+ ///
+ ///
+ ///
+ /// [LambdaFunction]
+ /// [HttpApiAuthorizer(Name = "MyAuthorizer")]
+ /// public APIGatewayCustomAuthorizerV2SimpleResponse Authorize(APIGatewayCustomAuthorizerV2Request request)
+ /// {
+ /// // Validate token and return authorization response
+ /// }
+ ///
+ /// [LambdaFunction]
+ /// [HttpApi(LambdaHttpMethod.Get, "/api/protected", Authorizer = "MyAuthorizer")]
+ /// public string ProtectedEndpoint()
+ /// {
+ /// return "Hello, authenticated user!";
+ /// }
+ ///
+ ///
+ [AttributeUsage(AttributeTargets.Method)]
+ public class HttpApiAuthorizerAttribute : Attribute
+ {
+ ///
+ /// Required. Unique name to identify this authorizer. Other functions reference this name
+ /// via the property.
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// Header name to use as identity source. Defaults to "Authorization".
+ /// The generator translates this to "$request.header.{IdentityHeader}" for CloudFormation.
+ ///
+ public string IdentityHeader { get; set; } = "Authorization";
+
+ ///
+ /// Whether to use simple responses (IsAuthorized: true/false) or IAM policy responses.
+ /// Defaults to true for simpler implementation.
+ ///
+ ///
+ /// When true, the authorizer should return .
+ /// When false, the authorizer should return .
+ ///
+ public bool EnableSimpleResponses { get; set; } = true;
+
+ ///
+ /// Authorizer payload format version. Valid values: "1.0" or "2.0".
+ /// Defaults to "2.0".
+ ///
+ public string PayloadFormatVersion { get; set; } = "2.0";
+
+ ///
+ /// TTL in seconds for caching authorizer results. 0 = no caching. Max = 3600.
+ /// Defaults to 0 (no caching).
+ ///
+ public int ResultTtlInSeconds { get; set; } = 0;
+ }
+}
\ No newline at end of file
diff --git a/Libraries/src/Amazon.Lambda.Annotations/APIGateway/RestApiAttribute.cs b/Libraries/src/Amazon.Lambda.Annotations/APIGateway/RestApiAttribute.cs
index 85e91a516..be1cbebac 100644
--- a/Libraries/src/Amazon.Lambda.Annotations/APIGateway/RestApiAttribute.cs
+++ b/Libraries/src/Amazon.Lambda.Annotations/APIGateway/RestApiAttribute.cs
@@ -19,6 +19,13 @@ public class RestApiAttribute : Attribute
///
public LambdaHttpMethod Method { get; set; }
+ ///
+ /// Name of the REST API Lambda authorizer to protect this endpoint.
+ /// Must match the Name property of a in this project.
+ /// Leave null/empty for public (unauthenticated) endpoints.
+ ///
+ public string Authorizer { get; set; }
+
///
/// Constructs a
///
diff --git a/Libraries/src/Amazon.Lambda.Annotations/APIGateway/RestApiAuthorizerAttribute.cs b/Libraries/src/Amazon.Lambda.Annotations/APIGateway/RestApiAuthorizerAttribute.cs
new file mode 100644
index 000000000..611b3be14
--- /dev/null
+++ b/Libraries/src/Amazon.Lambda.Annotations/APIGateway/RestApiAuthorizerAttribute.cs
@@ -0,0 +1,76 @@
+using System;
+
+namespace Amazon.Lambda.Annotations.APIGateway
+{
+ ///
+ /// Type of REST API Lambda authorizer
+ ///
+ public enum RestApiAuthorizerType
+ {
+ ///
+ /// Token-based authorizer. Receives the token directly from the identity source.
+ /// The token is available via request.AuthorizationToken.
+ ///
+ Token,
+
+ ///
+ /// Request-based authorizer. Receives the full request context including
+ /// headers, query strings, path parameters, and stage variables.
+ ///
+ Request
+ }
+
+ ///
+ /// Marks this Lambda function as a REST API (API Gateway V1) authorizer.
+ /// Other functions can reference this authorizer using the RestApi attribute's Authorizer property.
+ ///
+ ///
+ /// This attribute must be used in conjunction with the .
+ /// The authorizer function should return .
+ ///
+ ///
+ ///
+ /// [LambdaFunction]
+ /// [RestApiAuthorizer(Name = "TokenAuthorizer", Type = RestApiAuthorizerType.Token)]
+ /// public APIGatewayCustomAuthorizerResponse Authorize(APIGatewayCustomAuthorizerRequest request)
+ /// {
+ /// var token = request.AuthorizationToken;
+ /// // Validate token and return IAM policy response
+ /// }
+ ///
+ /// [LambdaFunction]
+ /// [RestApi(LambdaHttpMethod.Get, "/api/protected", Authorizer = "TokenAuthorizer")]
+ /// public string ProtectedEndpoint()
+ /// {
+ /// return "Hello, authenticated user!";
+ /// }
+ ///
+ ///
+ [AttributeUsage(AttributeTargets.Method)]
+ public class RestApiAuthorizerAttribute : Attribute
+ {
+ ///
+ /// Required. Unique name to identify this authorizer. Other functions reference this name
+ /// via the property.
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// Header name to use as identity source. Defaults to "Authorization".
+ /// The generator translates this to "method.request.header.{IdentityHeader}" for CloudFormation.
+ ///
+ public string IdentityHeader { get; set; } = "Authorization";
+
+ ///
+ /// Type of authorizer: Token or Request. Defaults to Token.
+ /// Token authorizers receive just the token value; Request authorizers receive full request context.
+ ///
+ public RestApiAuthorizerType Type { get; set; } = RestApiAuthorizerType.Token;
+
+ ///
+ /// TTL in seconds for caching authorizer results. 0 = no caching. Max = 3600.
+ /// Defaults to 0 (no caching).
+ ///
+ public int ResultTtlInSeconds { get; set; } = 0;
+ }
+}
\ No newline at end of file
diff --git a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/WriterTests/CloudFormationWriterTests.cs b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/WriterTests/CloudFormationWriterTests.cs
index 965b61e1c..df51d12f3 100644
--- a/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/WriterTests/CloudFormationWriterTests.cs
+++ b/Libraries/test/Amazon.Lambda.Annotations.SourceGenerators.Tests/WriterTests/CloudFormationWriterTests.cs
@@ -874,6 +874,7 @@ public class LambdaFunctionModelTest : ILambdaFunctionSerializable
public string SourceGeneratorVersion { get; set; }
public LambdaPackageType PackageType { get; set; } = LambdaPackageType.Zip;
public string ReturnTypeFullName { get; set; } = "void";
+ public string Authorizer { get; set; }
}
}
}
\ No newline at end of file
diff --git a/Libraries/test/TestCustomAuthorizerApp/AuthorizerFunction.cs b/Libraries/test/TestCustomAuthorizerApp/AuthorizerFunction.cs
index 0b6193563..1fc03f2e1 100644
--- a/Libraries/test/TestCustomAuthorizerApp/AuthorizerFunction.cs
+++ b/Libraries/test/TestCustomAuthorizerApp/AuthorizerFunction.cs
@@ -1,3 +1,5 @@
+using Amazon.Lambda.Annotations;
+using Amazon.Lambda.Annotations.APIGateway;
using Amazon.Lambda.APIGatewayEvents;
using Amazon.Lambda.Core;
@@ -34,6 +36,12 @@ public class AuthorizerFunction
/// HTTP API Lambda Authorizer (Payload format 2.0 with simple response)
/// Returns authorized status along with custom context that can be accessed via [FromCustomAuthorizer]
///
+ [LambdaFunction(ResourceName = "CustomAuthorizer")]
+ [HttpApiAuthorizer(
+ Name = "HttpApiLambdaAuthorizer",
+ IdentityHeader = "authorization",
+ EnableSimpleResponses = true,
+ PayloadFormatVersion = "2.0")]
public APIGatewayCustomAuthorizerV2SimpleResponse HttpApiAuthorize(
APIGatewayCustomAuthorizerV2Request request,
ILambdaContext context)
@@ -110,6 +118,11 @@ public APIGatewayCustomAuthorizerV2SimpleResponse HttpApiAuthorize(
/// REST API Lambda Authorizer (Token-based authorizer)
/// Returns an IAM policy document along with custom context values
///
+ [LambdaFunction(ResourceName = "RestApiAuthorizer")]
+ [RestApiAuthorizer(
+ Name = "RestApiLambdaAuthorizer",
+ Type = RestApiAuthorizerType.Token,
+ IdentityHeader = "Authorization")]
public APIGatewayCustomAuthorizerResponse RestApiAuthorize(
APIGatewayCustomAuthorizerRequest request,
ILambdaContext context)
@@ -215,4 +228,4 @@ private APIGatewayCustomAuthorizerResponse GenerateDenyPolicy(string principalId
}
};
}
-}
+}
\ No newline at end of file
diff --git a/Libraries/test/TestCustomAuthorizerApp/ProtectedFunction.cs b/Libraries/test/TestCustomAuthorizerApp/ProtectedFunction.cs
index d7a25ba65..39734a579 100644
--- a/Libraries/test/TestCustomAuthorizerApp/ProtectedFunction.cs
+++ b/Libraries/test/TestCustomAuthorizerApp/ProtectedFunction.cs
@@ -17,7 +17,7 @@ public class ProtectedFunction
/// Debug endpoint to see what's in the RequestContext.Authorizer
///
[LambdaFunction(ResourceName = "ProtectedEndpoint")]
- [HttpApi(LambdaHttpMethod.Get, "/api/protected")]
+ [HttpApi(LambdaHttpMethod.Get, "/api/protected", Authorizer = "HttpApiLambdaAuthorizer")]
public string GetProtectedData(
APIGatewayHttpApiV2ProxyRequest request,
ILambdaContext context)
@@ -87,7 +87,7 @@ public string GetProtectedData(
/// Another protected endpoint showing different usage - just getting the email.
///
[LambdaFunction(ResourceName = "GetUserInfo")]
- [HttpApi(LambdaHttpMethod.Get, "/api/user-info")]
+ [HttpApi(LambdaHttpMethod.Get, "/api/user-info", Authorizer = "HttpApiLambdaAuthorizer")]
public object GetUserInfo(
[FromCustomAuthorizer(Name = "userId")] string userId,
[FromCustomAuthorizer(Name = "email")] string email,
@@ -122,7 +122,7 @@ public string HealthCheck(ILambdaContext context)
/// REST API authorizers use a different context structure than HTTP API v2.
///
[LambdaFunction(ResourceName = "RestUserInfo")]
- [RestApi(LambdaHttpMethod.Get, "/api/rest-user-info")]
+ [RestApi(LambdaHttpMethod.Get, "/api/rest-user-info", Authorizer = "RestApiLambdaAuthorizer")]
public object GetRestUserInfo(
[FromCustomAuthorizer(Name = "userId")] string userId,
[FromCustomAuthorizer(Name = "email")] string email,
@@ -148,7 +148,7 @@ public object GetRestUserInfo(
/// where RequestContext.Authorizer is a dictionary, not RequestContext.Authorizer.Lambda.
///
[LambdaFunction(ResourceName = "HttpApiV1UserInfo")]
- [HttpApi(LambdaHttpMethod.Get, "/api/http-v1-user-info", Version = HttpApiVersion.V1)]
+ [HttpApi(LambdaHttpMethod.Get, "/api/http-v1-user-info", Version = HttpApiVersion.V1, Authorizer = "HttpApiLambdaAuthorizer")]
public object GetHttpApiV1UserInfo(
[FromCustomAuthorizer(Name = "userId")] string userId,
[FromCustomAuthorizer(Name = "email")] string email,
@@ -174,7 +174,7 @@ public object GetHttpApiV1UserInfo(
/// when authorizer context is missing (the handler returns Stream, not response object).
///
[LambdaFunction(ResourceName = "IHttpResultUserInfo")]
- [HttpApi(LambdaHttpMethod.Get, "/api/ihttpresult-user-info")]
+ [HttpApi(LambdaHttpMethod.Get, "/api/ihttpresult-user-info", Authorizer = "HttpApiLambdaAuthorizer")]
public IHttpResult GetIHttpResult(
[FromCustomAuthorizer(Name = "userId")] string userId,
[FromCustomAuthorizer(Name = "email")] string email,
@@ -196,7 +196,7 @@ public IHttpResult GetIHttpResult(
/// the Lambda authorizer context.
///
[LambdaFunction(ResourceName = "NonStringUserInfo")]
- [HttpApi(LambdaHttpMethod.Get, "/api/nonstring-user-info")]
+ [HttpApi(LambdaHttpMethod.Get, "/api/nonstring-user-info", Authorizer = "HttpApiLambdaAuthorizer")]
public object GetNonStringUserInfo(
APIGatewayHttpApiV2ProxyRequest request,
[FromCustomAuthorizer(Name = "numericTenantId")] int tenantId,
@@ -244,4 +244,4 @@ public object GetNonStringUserInfo(
Message = "Successfully extracted non-string types from custom authorizer context!"
};
}
-}
+}
\ No newline at end of file
diff --git a/Libraries/test/TestCustomAuthorizerApp/aws-lambda-tools-defaults.json b/Libraries/test/TestCustomAuthorizerApp/aws-lambda-tools-defaults.json
index 53198ee82..365878413 100644
--- a/Libraries/test/TestCustomAuthorizerApp/aws-lambda-tools-defaults.json
+++ b/Libraries/test/TestCustomAuthorizerApp/aws-lambda-tools-defaults.json
@@ -8,8 +8,8 @@
"configuration": "Release",
"template": "serverless.template",
"template-parameters": "",
- "s3-bucket": "test-custom-authorizer-app",
+"s3-bucket" : "test-custom-authorizer-85104052",
"s3-prefix": "TestCustomAuthorizerApp/",
- "stack-name": "test-custom-authorizer",
- "function-architecture": "x86_64"
-}
\ No newline at end of file
+"stack-name" : "test-custom-authorizer-85104052",
+"function-architecture" : "x86_64"
+}
diff --git a/Libraries/test/TestCustomAuthorizerApp/serverless.template b/Libraries/test/TestCustomAuthorizerApp/serverless.template
index 56ae0368d..53913b3b0 100644
--- a/Libraries/test/TestCustomAuthorizerApp/serverless.template
+++ b/Libraries/test/TestCustomAuthorizerApp/serverless.template
@@ -1,622 +1,388 @@
{
"AWSTemplateFormatVersion": "2010-09-09",
"Transform": "AWS::Serverless-2016-10-31",
- "Description": "Test application demonstrating FromCustomAuthorizer attribute with both HTTP API and REST API Lambda Authorizers. This template is partially managed by Amazon.Lambda.Annotations (v1.9.0.0).",
- "Globals": {
- "Function": {
- "Runtime": "dotnet8",
- "MemorySize": 512,
- "Timeout": 30,
- "CodeUri": "."
- }
- },
+ "Description": "This template is partially managed by Amazon.Lambda.Annotations (v1.9.0.0).",
"Resources": {
- "CustomAuthorizer": {
- "Type": "AWS::Serverless::Function",
- "Properties": {
- "Handler": "TestCustomAuthorizerApp::TestCustomAuthorizerApp.AuthorizerFunction::HttpApiAuthorize",
- "Description": "Lambda Authorizer that validates requests and provides custom context values",
- "Policies": [
- "AWSLambdaBasicExecutionRole"
- ],
- "Environment": {
- "Variables": {
- "LAMBDA_NET_SERIALIZER_DEBUG": "true"
+ "CustomHttpApi": {
+ "Type": "AWS::Serverless::HttpApi",
+ "Metadata": {
+ "Tool": "Amazon.Lambda.Annotations"
+ },
+ "Properties": {
+ "Auth": {
+ "Authorizers": {
+ "HttpApiLambdaAuthorizer": {
+ "FunctionArn": {
+ "Fn::GetAtt": [
+ "CustomAuthorizer",
+ "Arn"
+ ]
+ },
+ "AuthorizerPayloadFormatVersion": "2.0",
+ "EnableSimpleResponses": true,
+ "Identity": {
+ "Headers": [
+ "authorization"
+ ]
+ }
+ }
}
}
}
},
- "CustomAuthorizerPermission": {
- "Type": "AWS::Lambda::Permission",
- "Properties": {
- "FunctionName": {
- "Ref": "CustomAuthorizer"
- },
- "Action": "lambda:InvokeFunction",
- "Principal": "apigateway.amazonaws.com",
- "SourceArn": {
- "Fn::Sub": "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ServerlessHttpApi}/authorizers/*"
+ "CustomRestApi": {
+ "Type": "AWS::Serverless::Api",
+ "Properties": {
+ "StageName": "Prod",
+ "Auth": {
+ "Authorizers": {
+ "RestApiLambdaAuthorizer": {
+ "FunctionArn": {
+ "Fn::GetAtt": [
+ "RestApiAuthorizer",
+ "Arn"
+ ]
+ },
+ "Identity": {
+ "Header": "Authorization"
+ },
+ "FunctionPayloadType": "TOKEN"
+ }
+ }
}
+ },
+ "Metadata": {
+ "Tool": "Amazon.Lambda.Annotations"
}
},
- "ServerlessHttpApi": {
- "Type": "AWS::ApiGatewayV2::Api",
- "Properties": {
- "Name": "TestCustomAuthorizerApi",
- "ProtocolType": "HTTP",
- "Description": "HTTP API with custom Lambda authorizer for testing FromCustomAuthorizer attribute"
- }
- },
- "HttpApiAuthorizer": {
- "Type": "AWS::ApiGatewayV2::Authorizer",
+ "CustomAuthorizer": {
+ "Type": "AWS::Serverless::Function",
+ "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/${CustomAuthorizer.Arn}/invocations"
- },
- "AuthorizerPayloadFormatVersion": "2.0",
- "EnableSimpleResponses": true,
- "IdentitySource": [
- "$request.header.authorization"
+ "Runtime": "dotnet8",
+ "CodeUri": ".",
+ "MemorySize": 512,
+ "Timeout": 30,
+ "Policies": [
+ "AWSLambdaBasicExecutionRole"
],
- "Name": "CustomLambdaAuthorizer"
+ "PackageType": "Zip",
+ "Handler": "TestCustomAuthorizerApp::TestCustomAuthorizerApp.AuthorizerFunction_HttpApiAuthorize_Generated::HttpApiAuthorize"
}
},
- "ProtectedEndpoint": {
+ "RestApiAuthorizer": {
"Type": "AWS::Serverless::Function",
+ "Metadata": {
+ "Tool": "Amazon.Lambda.Annotations"
+ },
"Properties": {
- "Handler": "TestCustomAuthorizerApp::TestCustomAuthorizerApp.ProtectedFunction_GetProtectedData_Generated::GetProtectedData",
- "Description": "Protected endpoint demonstrating FromCustomAuthorizer attribute",
- "PackageType": "Zip",
- "CodeUri": ".",
"Runtime": "dotnet8",
+ "CodeUri": ".",
+ "MemorySize": 512,
+ "Timeout": 30,
"Policies": [
"AWSLambdaBasicExecutionRole"
- ]
- }
- },
- "ProtectedEndpointPermission": {
- "Type": "AWS::Lambda::Permission",
- "Properties": {
- "FunctionName": {
- "Ref": "ProtectedEndpoint"
- },
- "Action": "lambda:InvokeFunction",
- "Principal": "apigateway.amazonaws.com",
- "SourceArn": {
- "Fn::Sub": "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ServerlessHttpApi}/*/*/api/protected"
- }
+ ],
+ "PackageType": "Zip",
+ "Handler": "TestCustomAuthorizerApp::TestCustomAuthorizerApp.AuthorizerFunction_RestApiAuthorize_Generated::RestApiAuthorize"
}
},
- "ProtectedEndpointIntegration": {
- "Type": "AWS::ApiGatewayV2::Integration",
- "Properties": {
- "ApiId": {
- "Ref": "ServerlessHttpApi"
- },
- "IntegrationType": "AWS_PROXY",
- "IntegrationUri": {
- "Fn::GetAtt": [
- "ProtectedEndpoint",
- "Arn"
+ "ProtectedEndpoint": {
+ "Type": "AWS::Serverless::Function",
+ "Metadata": {
+ "Tool": "Amazon.Lambda.Annotations",
+ "SyncedEvents": [
+ "RootGet"
+ ],
+ "SyncedEventProperties": {
+ "RootGet": [
+ "Path",
+ "Method",
+ "Auth.Authorizer",
+ "ApiId.Ref"
]
- },
- "PayloadFormatVersion": "2.0"
- }
- },
- "ProtectedEndpointRoute": {
- "Type": "AWS::ApiGatewayV2::Route",
- "Properties": {
- "ApiId": {
- "Ref": "ServerlessHttpApi"
- },
- "RouteKey": "GET /api/protected",
- "AuthorizationType": "CUSTOM",
- "AuthorizerId": {
- "Ref": "HttpApiAuthorizer"
- },
- "Target": {
- "Fn::Sub": "integrations/${ProtectedEndpointIntegration}"
}
- }
- },
- "GetUserInfo": {
- "Type": "AWS::Serverless::Function",
+ },
"Properties": {
- "Handler": "TestCustomAuthorizerApp::TestCustomAuthorizerApp.ProtectedFunction_GetUserInfo_Generated::GetUserInfo",
- "Description": "Returns user info from custom authorizer context",
- "PackageType": "Zip",
- "CodeUri": ".",
"Runtime": "dotnet8",
+ "CodeUri": ".",
+ "MemorySize": 512,
+ "Timeout": 30,
"Policies": [
"AWSLambdaBasicExecutionRole"
- ]
- }
- },
- "GetUserInfoPermission": {
- "Type": "AWS::Lambda::Permission",
- "Properties": {
- "FunctionName": {
- "Ref": "GetUserInfo"
- },
- "Action": "lambda:InvokeFunction",
- "Principal": "apigateway.amazonaws.com",
- "SourceArn": {
- "Fn::Sub": "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ServerlessHttpApi}/*/*/api/user-info"
+ ],
+ "PackageType": "Zip",
+ "Handler": "TestCustomAuthorizerApp::TestCustomAuthorizerApp.ProtectedFunction_GetProtectedData_Generated::GetProtectedData",
+ "Events": {
+ "RootGet": {
+ "Type": "HttpApi",
+ "Properties": {
+ "Path": "/api/protected",
+ "Method": "GET",
+ "Auth": {
+ "Authorizer": "HttpApiLambdaAuthorizer"
+ },
+ "ApiId": {
+ "Ref": "CustomHttpApi"
+ }
+ }
+ }
}
}
},
- "GetUserInfoIntegration": {
- "Type": "AWS::ApiGatewayV2::Integration",
- "Properties": {
- "ApiId": {
- "Ref": "ServerlessHttpApi"
- },
- "IntegrationType": "AWS_PROXY",
- "IntegrationUri": {
- "Fn::GetAtt": [
- "GetUserInfo",
- "Arn"
+ "GetUserInfo": {
+ "Type": "AWS::Serverless::Function",
+ "Metadata": {
+ "Tool": "Amazon.Lambda.Annotations",
+ "SyncedEvents": [
+ "RootGet"
+ ],
+ "SyncedEventProperties": {
+ "RootGet": [
+ "Path",
+ "Method",
+ "Auth.Authorizer",
+ "ApiId.Ref"
]
- },
- "PayloadFormatVersion": "2.0"
- }
- },
- "GetUserInfoRoute": {
- "Type": "AWS::ApiGatewayV2::Route",
- "Properties": {
- "ApiId": {
- "Ref": "ServerlessHttpApi"
- },
- "RouteKey": "GET /api/user-info",
- "AuthorizationType": "CUSTOM",
- "AuthorizerId": {
- "Ref": "HttpApiAuthorizer"
- },
- "Target": {
- "Fn::Sub": "integrations/${GetUserInfoIntegration}"
}
- }
- },
- "HealthCheck": {
- "Type": "AWS::Serverless::Function",
+ },
"Properties": {
- "Handler": "TestCustomAuthorizerApp::TestCustomAuthorizerApp.ProtectedFunction_HealthCheck_Generated::HealthCheck",
- "Description": "Simple health check endpoint without authorizer",
- "PackageType": "Zip",
- "CodeUri": ".",
"Runtime": "dotnet8",
+ "CodeUri": ".",
+ "MemorySize": 512,
+ "Timeout": 30,
"Policies": [
"AWSLambdaBasicExecutionRole"
- ]
- }
- },
- "HealthCheckPermission": {
- "Type": "AWS::Lambda::Permission",
- "Properties": {
- "FunctionName": {
- "Ref": "HealthCheck"
- },
- "Action": "lambda:InvokeFunction",
- "Principal": "apigateway.amazonaws.com",
- "SourceArn": {
- "Fn::Sub": "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ServerlessHttpApi}/*/*/api/health"
+ ],
+ "PackageType": "Zip",
+ "Handler": "TestCustomAuthorizerApp::TestCustomAuthorizerApp.ProtectedFunction_GetUserInfo_Generated::GetUserInfo",
+ "Events": {
+ "RootGet": {
+ "Type": "HttpApi",
+ "Properties": {
+ "Path": "/api/user-info",
+ "Method": "GET",
+ "Auth": {
+ "Authorizer": "HttpApiLambdaAuthorizer"
+ },
+ "ApiId": {
+ "Ref": "CustomHttpApi"
+ }
+ }
+ }
}
}
},
- "HealthCheckIntegration": {
- "Type": "AWS::ApiGatewayV2::Integration",
- "Properties": {
- "ApiId": {
- "Ref": "ServerlessHttpApi"
- },
- "IntegrationType": "AWS_PROXY",
- "IntegrationUri": {
- "Fn::GetAtt": [
- "HealthCheck",
- "Arn"
+ "HealthCheck": {
+ "Type": "AWS::Serverless::Function",
+ "Metadata": {
+ "Tool": "Amazon.Lambda.Annotations",
+ "SyncedEvents": [
+ "RootGet"
+ ],
+ "SyncedEventProperties": {
+ "RootGet": [
+ "Path",
+ "Method"
]
- },
- "PayloadFormatVersion": "2.0"
- }
- },
- "HealthCheckRoute": {
- "Type": "AWS::ApiGatewayV2::Route",
- "Properties": {
- "ApiId": {
- "Ref": "ServerlessHttpApi"
- },
- "RouteKey": "GET /api/health",
- "Target": {
- "Fn::Sub": "integrations/${HealthCheckIntegration}"
}
- }
- },
- "HttpApiStage": {
- "Type": "AWS::ApiGatewayV2::Stage",
+ },
"Properties": {
- "ApiId": {
- "Ref": "ServerlessHttpApi"
- },
- "StageName": "$default",
- "AutoDeploy": true
- }
- },
- "RestApiAuthorizer": {
- "Type": "AWS::Serverless::Function",
- "Properties": {
- "Handler": "TestCustomAuthorizerApp::TestCustomAuthorizerApp.AuthorizerFunction::RestApiAuthorize",
- "Description": "REST API Lambda Authorizer that validates requests and provides custom context values",
+ "Runtime": "dotnet8",
+ "CodeUri": ".",
+ "MemorySize": 512,
+ "Timeout": 30,
"Policies": [
"AWSLambdaBasicExecutionRole"
- ]
- }
- },
- "ServerlessRestApi": {
- "Type": "AWS::ApiGateway::RestApi",
- "Properties": {
- "Name": "TestCustomAuthorizerRestApi",
- "Description": "REST API with custom Lambda authorizer for testing FromCustomAuthorizer attribute"
- }
- },
- "RestApiLambdaAuthorizer": {
- "Type": "AWS::ApiGateway::Authorizer",
- "Properties": {
- "Name": "RestApiLambdaAuthorizer",
- "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"
- }
- },
- "RestApiAuthorizerPermission": {
- "Type": "AWS::Lambda::Permission",
- "Properties": {
- "FunctionName": {
- "Ref": "RestApiAuthorizer"
- },
- "Action": "lambda:InvokeFunction",
- "Principal": "apigateway.amazonaws.com",
- "SourceArn": {
- "Fn::Sub": "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ServerlessRestApi}/authorizers/*"
+ ],
+ "PackageType": "Zip",
+ "Handler": "TestCustomAuthorizerApp::TestCustomAuthorizerApp.ProtectedFunction_HealthCheck_Generated::HealthCheck",
+ "Events": {
+ "RootGet": {
+ "Type": "HttpApi",
+ "Properties": {
+ "Path": "/api/health",
+ "Method": "GET"
+ }
+ }
}
}
},
"RestUserInfo": {
"Type": "AWS::Serverless::Function",
+ "Metadata": {
+ "Tool": "Amazon.Lambda.Annotations",
+ "SyncedEvents": [
+ "RootGet"
+ ],
+ "SyncedEventProperties": {
+ "RootGet": [
+ "Path",
+ "Method",
+ "Auth.Authorizer",
+ "RestApiId.Ref"
+ ]
+ }
+ },
"Properties": {
- "Handler": "TestCustomAuthorizerApp::TestCustomAuthorizerApp.ProtectedFunction_GetRestUserInfo_Generated::GetRestUserInfo",
- "Description": "REST API endpoint demonstrating FromCustomAuthorizer attribute",
- "PackageType": "Zip",
- "CodeUri": ".",
"Runtime": "dotnet8",
+ "CodeUri": ".",
+ "MemorySize": 512,
+ "Timeout": 30,
"Policies": [
"AWSLambdaBasicExecutionRole"
- ]
- }
- },
- "RestUserInfoPermission": {
- "Type": "AWS::Lambda::Permission",
- "Properties": {
- "FunctionName": {
- "Ref": "RestUserInfo"
- },
- "Action": "lambda:InvokeFunction",
- "Principal": "apigateway.amazonaws.com",
- "SourceArn": {
- "Fn::Sub": "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ServerlessRestApi}/*/*/*"
- }
- }
- },
- "RestApiResource": {
- "Type": "AWS::ApiGateway::Resource",
- "Properties": {
- "RestApiId": {
- "Ref": "ServerlessRestApi"
- },
- "ParentId": {
- "Fn::GetAtt": [
- "ServerlessRestApi",
- "RootResourceId"
- ]
- },
- "PathPart": "api"
- }
- },
- "RestApiUserInfoResource": {
- "Type": "AWS::ApiGateway::Resource",
- "Properties": {
- "RestApiId": {
- "Ref": "ServerlessRestApi"
- },
- "ParentId": {
- "Ref": "RestApiResource"
- },
- "PathPart": "rest-user-info"
- }
- },
- "RestApiUserInfoMethod": {
- "Type": "AWS::ApiGateway::Method",
- "Properties": {
- "RestApiId": {
- "Ref": "ServerlessRestApi"
- },
- "ResourceId": {
- "Ref": "RestApiUserInfoResource"
- },
- "HttpMethod": "GET",
- "AuthorizationType": "CUSTOM",
- "AuthorizerId": {
- "Ref": "RestApiLambdaAuthorizer"
- },
- "Integration": {
- "Type": "AWS_PROXY",
- "IntegrationHttpMethod": "POST",
- "Uri": {
- "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${RestUserInfo.Arn}/invocations"
+ ],
+ "PackageType": "Zip",
+ "Handler": "TestCustomAuthorizerApp::TestCustomAuthorizerApp.ProtectedFunction_GetRestUserInfo_Generated::GetRestUserInfo",
+ "Events": {
+ "RootGet": {
+ "Type": "Api",
+ "Properties": {
+ "Path": "/api/rest-user-info",
+ "Method": "GET",
+ "Auth": {
+ "Authorizer": "RestApiLambdaAuthorizer"
+ },
+ "RestApiId": {
+ "Ref": "CustomRestApi"
+ }
+ }
}
}
}
},
- "RestApiDeployment": {
- "Type": "AWS::ApiGateway::Deployment",
- "DependsOn": [
- "RestApiUserInfoMethod"
- ],
- "Properties": {
- "RestApiId": {
- "Ref": "ServerlessRestApi"
- }
- }
- },
- "RestApiStage": {
- "Type": "AWS::ApiGateway::Stage",
- "Properties": {
- "RestApiId": {
- "Ref": "ServerlessRestApi"
- },
- "DeploymentId": {
- "Ref": "RestApiDeployment"
- },
- "StageName": "Prod"
- }
- },
"HttpApiV1UserInfo": {
"Type": "AWS::Serverless::Function",
+ "Metadata": {
+ "Tool": "Amazon.Lambda.Annotations",
+ "SyncedEvents": [
+ "RootGet"
+ ],
+ "SyncedEventProperties": {
+ "RootGet": [
+ "Path",
+ "Method",
+ "PayloadFormatVersion",
+ "Auth.Authorizer",
+ "ApiId.Ref"
+ ]
+ }
+ },
"Properties": {
- "Handler": "TestCustomAuthorizerApp::TestCustomAuthorizerApp.ProtectedFunction_GetHttpApiV1UserInfo_Generated::GetHttpApiV1UserInfo",
- "Description": "HTTP API v1 endpoint demonstrating FromCustomAuthorizer attribute with payload format 1.0",
- "PackageType": "Zip",
- "CodeUri": ".",
"Runtime": "dotnet8",
+ "CodeUri": ".",
+ "MemorySize": 512,
+ "Timeout": 30,
"Policies": [
"AWSLambdaBasicExecutionRole"
- ]
- }
- },
- "HttpApiV1UserInfoPermission": {
- "Type": "AWS::Lambda::Permission",
- "Properties": {
- "FunctionName": {
- "Ref": "HttpApiV1UserInfo"
- },
- "Action": "lambda:InvokeFunction",
- "Principal": "apigateway.amazonaws.com",
- "SourceArn": {
- "Fn::Sub": "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ServerlessHttpApi}/*/*/api/http-v1-user-info"
- }
- }
- },
- "HttpApiV1UserInfoIntegration": {
- "Type": "AWS::ApiGatewayV2::Integration",
- "Properties": {
- "ApiId": {
- "Ref": "ServerlessHttpApi"
- },
- "IntegrationType": "AWS_PROXY",
- "IntegrationUri": {
- "Fn::GetAtt": [
- "HttpApiV1UserInfo",
- "Arn"
- ]
- },
- "PayloadFormatVersion": "1.0"
- }
- },
- "HttpApiV1UserInfoRoute": {
- "Type": "AWS::ApiGatewayV2::Route",
- "Properties": {
- "ApiId": {
- "Ref": "ServerlessHttpApi"
- },
- "RouteKey": "GET /api/http-v1-user-info",
- "AuthorizationType": "CUSTOM",
- "AuthorizerId": {
- "Ref": "HttpApiAuthorizer"
- },
- "Target": {
- "Fn::Sub": "integrations/${HttpApiV1UserInfoIntegration}"
+ ],
+ "PackageType": "Zip",
+ "Handler": "TestCustomAuthorizerApp::TestCustomAuthorizerApp.ProtectedFunction_GetHttpApiV1UserInfo_Generated::GetHttpApiV1UserInfo",
+ "Events": {
+ "RootGet": {
+ "Type": "HttpApi",
+ "Properties": {
+ "Path": "/api/http-v1-user-info",
+ "Method": "GET",
+ "PayloadFormatVersion": "1.0",
+ "Auth": {
+ "Authorizer": "HttpApiLambdaAuthorizer"
+ },
+ "ApiId": {
+ "Ref": "CustomHttpApi"
+ }
+ }
+ }
}
}
},
"IHttpResultUserInfo": {
"Type": "AWS::Serverless::Function",
+ "Metadata": {
+ "Tool": "Amazon.Lambda.Annotations",
+ "SyncedEvents": [
+ "RootGet"
+ ],
+ "SyncedEventProperties": {
+ "RootGet": [
+ "Path",
+ "Method",
+ "Auth.Authorizer",
+ "ApiId.Ref"
+ ]
+ }
+ },
"Properties": {
- "Handler": "TestCustomAuthorizerApp::TestCustomAuthorizerApp.ProtectedFunction_GetIHttpResult_Generated::GetIHttpResult",
- "Description": "IHttpResult endpoint demonstrating FromCustomAuthorizer with IHttpResult return type",
- "PackageType": "Zip",
- "CodeUri": ".",
"Runtime": "dotnet8",
+ "CodeUri": ".",
+ "MemorySize": 512,
+ "Timeout": 30,
"Policies": [
"AWSLambdaBasicExecutionRole"
- ]
- }
- },
- "IHttpResultUserInfoPermission": {
- "Type": "AWS::Lambda::Permission",
- "Properties": {
- "FunctionName": {
- "Ref": "IHttpResultUserInfo"
- },
- "Action": "lambda:InvokeFunction",
- "Principal": "apigateway.amazonaws.com",
- "SourceArn": {
- "Fn::Sub": "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ServerlessHttpApi}/*/*/api/ihttpresult-user-info"
- }
- }
- },
- "IHttpResultUserInfoIntegration": {
- "Type": "AWS::ApiGatewayV2::Integration",
- "Properties": {
- "ApiId": {
- "Ref": "ServerlessHttpApi"
- },
- "IntegrationType": "AWS_PROXY",
- "IntegrationUri": {
- "Fn::GetAtt": [
- "IHttpResultUserInfo",
- "Arn"
- ]
- },
- "PayloadFormatVersion": "2.0"
- }
- },
- "IHttpResultUserInfoRoute": {
- "Type": "AWS::ApiGatewayV2::Route",
- "Properties": {
- "ApiId": {
- "Ref": "ServerlessHttpApi"
- },
- "RouteKey": "GET /api/ihttpresult-user-info",
- "AuthorizationType": "CUSTOM",
- "AuthorizerId": {
- "Ref": "HttpApiAuthorizer"
- },
- "Target": {
- "Fn::Sub": "integrations/${IHttpResultUserInfoIntegration}"
+ ],
+ "PackageType": "Zip",
+ "Handler": "TestCustomAuthorizerApp::TestCustomAuthorizerApp.ProtectedFunction_GetIHttpResult_Generated::GetIHttpResult",
+ "Events": {
+ "RootGet": {
+ "Type": "HttpApi",
+ "Properties": {
+ "Path": "/api/ihttpresult-user-info",
+ "Method": "GET",
+ "Auth": {
+ "Authorizer": "HttpApiLambdaAuthorizer"
+ },
+ "ApiId": {
+ "Ref": "CustomHttpApi"
+ }
+ }
+ }
}
}
},
"NonStringUserInfo": {
"Type": "AWS::Serverless::Function",
+ "Metadata": {
+ "Tool": "Amazon.Lambda.Annotations",
+ "SyncedEvents": [
+ "RootGet"
+ ],
+ "SyncedEventProperties": {
+ "RootGet": [
+ "Path",
+ "Method",
+ "Auth.Authorizer",
+ "ApiId.Ref"
+ ]
+ }
+ },
"Properties": {
- "Handler": "TestCustomAuthorizerApp::TestCustomAuthorizerApp.ProtectedFunction_GetNonStringUserInfo_Generated::GetNonStringUserInfo",
- "Description": "HTTP API v2 endpoint demonstrating FromCustomAuthorizer with non-string types (int, bool, double)",
- "PackageType": "Zip",
- "CodeUri": ".",
"Runtime": "dotnet8",
+ "CodeUri": ".",
+ "MemorySize": 512,
+ "Timeout": 30,
"Policies": [
"AWSLambdaBasicExecutionRole"
- ]
- }
- },
- "NonStringUserInfoPermission": {
- "Type": "AWS::Lambda::Permission",
- "Properties": {
- "FunctionName": {
- "Ref": "NonStringUserInfo"
- },
- "Action": "lambda:InvokeFunction",
- "Principal": "apigateway.amazonaws.com",
- "SourceArn": {
- "Fn::Sub": "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${ServerlessHttpApi}/*/*/api/nonstring-user-info"
- }
- }
- },
- "NonStringUserInfoIntegration": {
- "Type": "AWS::ApiGatewayV2::Integration",
- "Properties": {
- "ApiId": {
- "Ref": "ServerlessHttpApi"
- },
- "IntegrationType": "AWS_PROXY",
- "IntegrationUri": {
- "Fn::GetAtt": [
- "NonStringUserInfo",
- "Arn"
- ]
- },
- "PayloadFormatVersion": "2.0"
- }
- },
- "NonStringUserInfoRoute": {
- "Type": "AWS::ApiGatewayV2::Route",
- "Properties": {
- "ApiId": {
- "Ref": "ServerlessHttpApi"
- },
- "RouteKey": "GET /api/nonstring-user-info",
- "AuthorizationType": "CUSTOM",
- "AuthorizerId": {
- "Ref": "HttpApiAuthorizer"
- },
- "Target": {
- "Fn::Sub": "integrations/${NonStringUserInfoIntegration}"
+ ],
+ "PackageType": "Zip",
+ "Handler": "TestCustomAuthorizerApp::TestCustomAuthorizerApp.ProtectedFunction_GetNonStringUserInfo_Generated::GetNonStringUserInfo",
+ "Events": {
+ "RootGet": {
+ "Type": "HttpApi",
+ "Properties": {
+ "Path": "/api/nonstring-user-info",
+ "Method": "GET",
+ "Auth": {
+ "Authorizer": "HttpApiLambdaAuthorizer"
+ },
+ "ApiId": {
+ "Ref": "CustomHttpApi"
+ }
+ }
+ }
}
}
}
- },
- "Outputs": {
- "ApiUrl": {
- "Description": "HTTP API endpoint URL",
- "Value": {
- "Fn::Sub": "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com"
- }
- },
- "ProtectedEndpointUrl": {
- "Description": "Protected endpoint URL (requires Authorization header)",
- "Value": {
- "Fn::Sub": "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/api/protected"
- }
- },
- "UserInfoUrl": {
- "Description": "User info endpoint URL (requires Authorization header)",
- "Value": {
- "Fn::Sub": "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/api/user-info"
- }
- },
- "HealthCheckUrl": {
- "Description": "Health check endpoint URL (no auth required)",
- "Value": {
- "Fn::Sub": "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/api/health"
- }
- },
- "RestApiUrl": {
- "Description": "REST API endpoint URL",
- "Value": {
- "Fn::Sub": "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod"
- }
- },
- "RestUserInfoUrl": {
- "Description": "REST API User info endpoint URL (requires Authorization header)",
- "Value": {
- "Fn::Sub": "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/api/rest-user-info"
- }
- },
- "HttpApiV1UserInfoUrl": {
- "Description": "HTTP API v1 User info endpoint URL (requires Authorization header, uses payload format 1.0)",
- "Value": {
- "Fn::Sub": "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/api/http-v1-user-info"
- }
- },
- "IHttpResultUserInfoUrl": {
- "Description": "IHttpResult User info endpoint URL (requires Authorization header, demonstrates IHttpResult return type)",
- "Value": {
- "Fn::Sub": "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/api/ihttpresult-user-info"
- }
- },
- "NonStringUserInfoUrl": {
- "Description": "Non-string types User info endpoint URL (requires Authorization header, demonstrates int/bool/double type conversion)",
- "Value": {
- "Fn::Sub": "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/api/nonstring-user-info"
- }
- }
}
}
\ No newline at end of file
diff --git a/Libraries/test/TestCustomAuthorizerApp/src/Function/Function.cs b/Libraries/test/TestCustomAuthorizerApp/src/Function/Function.cs
new file mode 100644
index 000000000..8bfb9c2e7
--- /dev/null
+++ b/Libraries/test/TestCustomAuthorizerApp/src/Function/Function.cs
@@ -0,0 +1,16 @@
+using System;
+
+using Amazon.Lambda.Core;
+
+namespace Function
+{
+ public class Function
+ {
+ public dynamic FunctionHandler(dynamic eventTrigger)
+ {
+ Console.WriteLine(eventTrigger);
+
+ return new {};
+ }
+ }
+}
diff --git a/Libraries/test/TestCustomAuthorizerApp/src/Function/Function.csproj b/Libraries/test/TestCustomAuthorizerApp/src/Function/Function.csproj
new file mode 100644
index 000000000..1c95e7ced
--- /dev/null
+++ b/Libraries/test/TestCustomAuthorizerApp/src/Function/Function.csproj
@@ -0,0 +1,12 @@
+
+
+ net8.0
+ true
+ Lambda
+
+
+
+
+
+
+
diff --git a/Libraries/test/TestExecutableServerlessApp/serverless.template b/Libraries/test/TestExecutableServerlessApp/serverless.template
index ac43959b7..1912a4498 100644
--- a/Libraries/test/TestExecutableServerlessApp/serverless.template
+++ b/Libraries/test/TestExecutableServerlessApp/serverless.template
@@ -22,19 +22,10 @@
}
},
"Resources": {
- "TestServerlessAppCustomizeResponseExamplesOkResponseWithHeaderGenerated": {
+ "TestServerlessAppVoidExampleVoidReturnGenerated": {
"Type": "AWS::Serverless::Function",
"Metadata": {
- "Tool": "Amazon.Lambda.Annotations",
- "SyncedEvents": [
- "RootGet"
- ],
- "SyncedEventProperties": {
- "RootGet": [
- "Path",
- "Method"
- ]
- }
+ "Tool": "Amazon.Lambda.Annotations"
},
"Properties": {
"MemorySize": 512,
@@ -51,33 +42,15 @@
},
"Environment": {
"Variables": {
- "ANNOTATIONS_HANDLER": "OkResponseWithHeader"
- }
- },
- "Events": {
- "RootGet": {
- "Type": "Api",
- "Properties": {
- "Path": "/okresponsewithheader/{x}",
- "Method": "GET"
- }
+ "ANNOTATIONS_HANDLER": "VoidReturn"
}
}
}
},
- "TestServerlessAppCustomizeResponseExamplesOkResponseWithHeaderAsyncGenerated": {
+ "TestServerlessAppDynamicExampleDynamicReturnGenerated": {
"Type": "AWS::Serverless::Function",
"Metadata": {
- "Tool": "Amazon.Lambda.Annotations",
- "SyncedEvents": [
- "RootGet"
- ],
- "SyncedEventProperties": {
- "RootGet": [
- "Path",
- "Method"
- ]
- }
+ "Tool": "Amazon.Lambda.Annotations"
},
"Properties": {
"MemorySize": 512,
@@ -94,33 +67,15 @@
},
"Environment": {
"Variables": {
- "ANNOTATIONS_HANDLER": "OkResponseWithHeaderAsync"
- }
- },
- "Events": {
- "RootGet": {
- "Type": "Api",
- "Properties": {
- "Path": "/okresponsewithheaderasync/{x}",
- "Method": "GET"
- }
+ "ANNOTATIONS_HANDLER": "DynamicReturn"
}
}
}
},
- "TestServerlessAppCustomizeResponseExamplesNotFoundResponseWithHeaderV2Generated": {
+ "TestServerlessAppDynamicExampleDynamicInputGenerated": {
"Type": "AWS::Serverless::Function",
"Metadata": {
- "Tool": "Amazon.Lambda.Annotations",
- "SyncedEvents": [
- "RootGet"
- ],
- "SyncedEventProperties": {
- "RootGet": [
- "Path",
- "Method"
- ]
- }
+ "Tool": "Amazon.Lambda.Annotations"
},
"Properties": {
"MemorySize": 512,
@@ -137,21 +92,12 @@
},
"Environment": {
"Variables": {
- "ANNOTATIONS_HANDLER": "NotFoundResponseWithHeaderV2"
- }
- },
- "Events": {
- "RootGet": {
- "Type": "HttpApi",
- "Properties": {
- "Path": "/notfoundwithheaderv2/{x}",
- "Method": "GET"
- }
+ "ANNOTATIONS_HANDLER": "DynamicInput"
}
}
}
},
- "TestServerlessAppCustomizeResponseExamplesNotFoundResponseWithHeaderV2AsyncGenerated": {
+ "GreeterSayHello": {
"Type": "AWS::Serverless::Function",
"Metadata": {
"Tool": "Amazon.Lambda.Annotations",
@@ -161,12 +107,13 @@
"SyncedEventProperties": {
"RootGet": [
"Path",
- "Method"
+ "Method",
+ "PayloadFormatVersion"
]
}
},
"Properties": {
- "MemorySize": 512,
+ "MemorySize": 1024,
"Timeout": 30,
"Policies": [
"AWSLambdaBasicExecutionRole"
@@ -180,21 +127,22 @@
},
"Environment": {
"Variables": {
- "ANNOTATIONS_HANDLER": "NotFoundResponseWithHeaderV2Async"
+ "ANNOTATIONS_HANDLER": "SayHello"
}
},
"Events": {
"RootGet": {
"Type": "HttpApi",
"Properties": {
- "Path": "/notfoundwithheaderv2async/{x}",
- "Method": "GET"
+ "Path": "/Greeter/SayHello",
+ "Method": "GET",
+ "PayloadFormatVersion": "1.0"
}
}
}
}
},
- "TestServerlessAppCustomizeResponseExamplesNotFoundResponseWithHeaderV1Generated": {
+ "GreeterSayHelloAsync": {
"Type": "AWS::Serverless::Function",
"Metadata": {
"Tool": "Amazon.Lambda.Annotations",
@@ -211,7 +159,7 @@
},
"Properties": {
"MemorySize": 512,
- "Timeout": 30,
+ "Timeout": 50,
"Policies": [
"AWSLambdaBasicExecutionRole"
],
@@ -224,14 +172,14 @@
},
"Environment": {
"Variables": {
- "ANNOTATIONS_HANDLER": "NotFoundResponseWithHeaderV1"
+ "ANNOTATIONS_HANDLER": "SayHelloAsync"
}
},
"Events": {
"RootGet": {
"Type": "HttpApi",
"Properties": {
- "Path": "/notfoundwithheaderv1/{x}",
+ "Path": "/Greeter/SayHelloAsync",
"Method": "GET",
"PayloadFormatVersion": "1.0"
}
@@ -239,52 +187,29 @@
}
}
},
- "TestServerlessAppCustomizeResponseExamplesNotFoundResponseWithHeaderV1AsyncGenerated": {
+ "ToLower": {
"Type": "AWS::Serverless::Function",
"Metadata": {
- "Tool": "Amazon.Lambda.Annotations",
- "SyncedEvents": [
- "RootGet"
- ],
- "SyncedEventProperties": {
- "RootGet": [
- "Path",
- "Method",
- "PayloadFormatVersion"
- ]
- }
+ "Tool": "Amazon.Lambda.Annotations"
},
"Properties": {
+ "Runtime": "provided.al2",
+ "CodeUri": ".",
"MemorySize": 512,
"Timeout": 30,
"Policies": [
"AWSLambdaBasicExecutionRole"
],
- "PackageType": "Image",
- "ImageUri": ".",
- "ImageConfig": {
- "Command": [
- "TestExecutableServerlessApp"
- ]
- },
+ "PackageType": "Zip",
+ "Handler": "TestExecutableServerlessApp",
"Environment": {
"Variables": {
- "ANNOTATIONS_HANDLER": "NotFoundResponseWithHeaderV1Async"
- }
- },
- "Events": {
- "RootGet": {
- "Type": "HttpApi",
- "Properties": {
- "Path": "/notfoundwithheaderv1async/{x}",
- "Method": "GET",
- "PayloadFormatVersion": "1.0"
- }
+ "ANNOTATIONS_HANDLER": "ToLower"
}
}
}
},
- "TestServerlessAppDynamicExampleDynamicReturnGenerated": {
+ "TestServerlessAppIntrinsicExampleHasIntrinsicGenerated": {
"Type": "AWS::Serverless::Function",
"Metadata": {
"Tool": "Amazon.Lambda.Annotations"
@@ -304,37 +229,34 @@
},
"Environment": {
"Variables": {
- "ANNOTATIONS_HANDLER": "DynamicReturn"
+ "ANNOTATIONS_HANDLER": "HasIntrinsic"
}
}
}
},
- "TestServerlessAppDynamicExampleDynamicInputGenerated": {
+ "TestServerlessAppParameterlessMethodWithResponseNoParameterWithResponseGenerated": {
"Type": "AWS::Serverless::Function",
"Metadata": {
"Tool": "Amazon.Lambda.Annotations"
},
"Properties": {
+ "Runtime": "provided.al2",
+ "CodeUri": ".",
"MemorySize": 512,
"Timeout": 30,
"Policies": [
"AWSLambdaBasicExecutionRole"
],
- "PackageType": "Image",
- "ImageUri": ".",
- "ImageConfig": {
- "Command": [
- "TestExecutableServerlessApp"
- ]
- },
+ "PackageType": "Zip",
+ "Handler": "TestExecutableServerlessApp",
"Environment": {
"Variables": {
- "ANNOTATIONS_HANDLER": "DynamicInput"
+ "ANNOTATIONS_HANDLER": "NoParameterWithResponse"
}
}
}
},
- "GreeterSayHello": {
+ "TestServerlessAppCustomizeResponseExamplesOkResponseWithHeaderGenerated": {
"Type": "AWS::Serverless::Function",
"Metadata": {
"Tool": "Amazon.Lambda.Annotations",
@@ -344,13 +266,12 @@
"SyncedEventProperties": {
"RootGet": [
"Path",
- "Method",
- "PayloadFormatVersion"
+ "Method"
]
}
},
"Properties": {
- "MemorySize": 1024,
+ "MemorySize": 512,
"Timeout": 30,
"Policies": [
"AWSLambdaBasicExecutionRole"
@@ -364,22 +285,21 @@
},
"Environment": {
"Variables": {
- "ANNOTATIONS_HANDLER": "SayHello"
+ "ANNOTATIONS_HANDLER": "OkResponseWithHeader"
}
},
"Events": {
"RootGet": {
- "Type": "HttpApi",
+ "Type": "Api",
"Properties": {
- "Path": "/Greeter/SayHello",
- "Method": "GET",
- "PayloadFormatVersion": "1.0"
+ "Path": "/okresponsewithheader/{x}",
+ "Method": "GET"
}
}
}
}
},
- "GreeterSayHelloAsync": {
+ "TestServerlessAppCustomizeResponseExamplesOkResponseWithHeaderAsyncGenerated": {
"Type": "AWS::Serverless::Function",
"Metadata": {
"Tool": "Amazon.Lambda.Annotations",
@@ -389,14 +309,13 @@
"SyncedEventProperties": {
"RootGet": [
"Path",
- "Method",
- "PayloadFormatVersion"
+ "Method"
]
}
},
"Properties": {
"MemorySize": 512,
- "Timeout": 50,
+ "Timeout": 30,
"Policies": [
"AWSLambdaBasicExecutionRole"
],
@@ -409,25 +328,33 @@
},
"Environment": {
"Variables": {
- "ANNOTATIONS_HANDLER": "SayHelloAsync"
+ "ANNOTATIONS_HANDLER": "OkResponseWithHeaderAsync"
}
},
"Events": {
"RootGet": {
- "Type": "HttpApi",
+ "Type": "Api",
"Properties": {
- "Path": "/Greeter/SayHelloAsync",
- "Method": "GET",
- "PayloadFormatVersion": "1.0"
+ "Path": "/okresponsewithheaderasync/{x}",
+ "Method": "GET"
}
}
}
}
},
- "TestServerlessAppIntrinsicExampleHasIntrinsicGenerated": {
+ "TestServerlessAppCustomizeResponseExamplesNotFoundResponseWithHeaderV2Generated": {
"Type": "AWS::Serverless::Function",
"Metadata": {
- "Tool": "Amazon.Lambda.Annotations"
+ "Tool": "Amazon.Lambda.Annotations",
+ "SyncedEvents": [
+ "RootGet"
+ ],
+ "SyncedEventProperties": {
+ "RootGet": [
+ "Path",
+ "Method"
+ ]
+ }
},
"Properties": {
"MemorySize": 512,
@@ -444,12 +371,21 @@
},
"Environment": {
"Variables": {
- "ANNOTATIONS_HANDLER": "HasIntrinsic"
+ "ANNOTATIONS_HANDLER": "NotFoundResponseWithHeaderV2"
+ }
+ },
+ "Events": {
+ "RootGet": {
+ "Type": "HttpApi",
+ "Properties": {
+ "Path": "/notfoundwithheaderv2/{x}",
+ "Method": "GET"
+ }
}
}
}
},
- "TestServerlessAppNullableReferenceTypeExampleNullableHeaderHttpApiGenerated": {
+ "TestServerlessAppCustomizeResponseExamplesNotFoundResponseWithHeaderV2AsyncGenerated": {
"Type": "AWS::Serverless::Function",
"Metadata": {
"Tool": "Amazon.Lambda.Annotations",
@@ -478,65 +414,111 @@
},
"Environment": {
"Variables": {
- "ANNOTATIONS_HANDLER": "NullableHeaderHttpApi"
+ "ANNOTATIONS_HANDLER": "NotFoundResponseWithHeaderV2Async"
}
},
"Events": {
"RootGet": {
"Type": "HttpApi",
"Properties": {
- "Path": "/nullableheaderhttpapi",
+ "Path": "/notfoundwithheaderv2async/{x}",
"Method": "GET"
}
}
}
}
},
- "TestServerlessAppParameterlessMethodsNoParameterGenerated": {
+ "TestServerlessAppCustomizeResponseExamplesNotFoundResponseWithHeaderV1Generated": {
"Type": "AWS::Serverless::Function",
"Metadata": {
- "Tool": "Amazon.Lambda.Annotations"
+ "Tool": "Amazon.Lambda.Annotations",
+ "SyncedEvents": [
+ "RootGet"
+ ],
+ "SyncedEventProperties": {
+ "RootGet": [
+ "Path",
+ "Method",
+ "PayloadFormatVersion"
+ ]
+ }
},
"Properties": {
- "Runtime": "provided.al2",
- "CodeUri": ".",
"MemorySize": 512,
"Timeout": 30,
"Policies": [
"AWSLambdaBasicExecutionRole"
],
- "PackageType": "Zip",
- "Handler": "TestExecutableServerlessApp",
+ "PackageType": "Image",
+ "ImageUri": ".",
+ "ImageConfig": {
+ "Command": [
+ "TestExecutableServerlessApp"
+ ]
+ },
"Environment": {
"Variables": {
- "ANNOTATIONS_HANDLER": "NoParameter"
+ "ANNOTATIONS_HANDLER": "NotFoundResponseWithHeaderV1"
+ }
+ },
+ "Events": {
+ "RootGet": {
+ "Type": "HttpApi",
+ "Properties": {
+ "Path": "/notfoundwithheaderv1/{x}",
+ "Method": "GET",
+ "PayloadFormatVersion": "1.0"
+ }
}
}
}
},
- "TestServerlessAppParameterlessMethodWithResponseNoParameterWithResponseGenerated": {
+ "TestServerlessAppCustomizeResponseExamplesNotFoundResponseWithHeaderV1AsyncGenerated": {
"Type": "AWS::Serverless::Function",
"Metadata": {
- "Tool": "Amazon.Lambda.Annotations"
+ "Tool": "Amazon.Lambda.Annotations",
+ "SyncedEvents": [
+ "RootGet"
+ ],
+ "SyncedEventProperties": {
+ "RootGet": [
+ "Path",
+ "Method",
+ "PayloadFormatVersion"
+ ]
+ }
},
"Properties": {
- "Runtime": "provided.al2",
- "CodeUri": ".",
"MemorySize": 512,
"Timeout": 30,
"Policies": [
"AWSLambdaBasicExecutionRole"
],
- "PackageType": "Zip",
- "Handler": "TestExecutableServerlessApp",
+ "PackageType": "Image",
+ "ImageUri": ".",
+ "ImageConfig": {
+ "Command": [
+ "TestExecutableServerlessApp"
+ ]
+ },
"Environment": {
"Variables": {
- "ANNOTATIONS_HANDLER": "NoParameterWithResponse"
+ "ANNOTATIONS_HANDLER": "NotFoundResponseWithHeaderV1Async"
+ }
+ },
+ "Events": {
+ "RootGet": {
+ "Type": "HttpApi",
+ "Properties": {
+ "Path": "/notfoundwithheaderv1async/{x}",
+ "Method": "GET",
+ "PayloadFormatVersion": "1.0"
+ }
}
}
}
},
- "TestExecutableServerlessAppSourceGenerationSerializationExampleGetPersonGenerated": {
+ "TestServerlessAppNullableReferenceTypeExampleNullableHeaderHttpApiGenerated": {
"Type": "AWS::Serverless::Function",
"Metadata": {
"Tool": "Amazon.Lambda.Annotations",
@@ -565,14 +547,14 @@
},
"Environment": {
"Variables": {
- "ANNOTATIONS_HANDLER": "GetPerson"
+ "ANNOTATIONS_HANDLER": "NullableHeaderHttpApi"
}
},
"Events": {
"RootGet": {
- "Type": "Api",
+ "Type": "HttpApi",
"Properties": {
- "Path": "/",
+ "Path": "/nullableheaderhttpapi",
"Method": "GET"
}
}
@@ -604,7 +586,7 @@
}
}
},
- "ToLower": {
+ "TestServerlessAppParameterlessMethodsNoParameterGenerated": {
"Type": "AWS::Serverless::Function",
"Metadata": {
"Tool": "Amazon.Lambda.Annotations"
@@ -621,7 +603,7 @@
"Handler": "TestExecutableServerlessApp",
"Environment": {
"Variables": {
- "ANNOTATIONS_HANDLER": "ToLower"
+ "ANNOTATIONS_HANDLER": "NoParameter"
}
}
}
@@ -651,10 +633,19 @@
}
}
},
- "TestServerlessAppVoidExampleVoidReturnGenerated": {
+ "TestExecutableServerlessAppSourceGenerationSerializationExampleGetPersonGenerated": {
"Type": "AWS::Serverless::Function",
"Metadata": {
- "Tool": "Amazon.Lambda.Annotations"
+ "Tool": "Amazon.Lambda.Annotations",
+ "SyncedEvents": [
+ "RootGet"
+ ],
+ "SyncedEventProperties": {
+ "RootGet": [
+ "Path",
+ "Method"
+ ]
+ }
},
"Properties": {
"MemorySize": 512,
@@ -671,7 +662,16 @@
},
"Environment": {
"Variables": {
- "ANNOTATIONS_HANDLER": "VoidReturn"
+ "ANNOTATIONS_HANDLER": "GetPerson"
+ }
+ },
+ "Events": {
+ "RootGet": {
+ "Type": "Api",
+ "Properties": {
+ "Path": "/",
+ "Method": "GET"
+ }
}
}
}
diff --git a/Libraries/test/TestServerlessApp/AuthorizerFunctions.cs b/Libraries/test/TestServerlessApp/AuthorizerFunctions.cs
new file mode 100644
index 000000000..2e0011a1b
--- /dev/null
+++ b/Libraries/test/TestServerlessApp/AuthorizerFunctions.cs
@@ -0,0 +1,192 @@
+using Amazon.Lambda.Annotations;
+using Amazon.Lambda.Annotations.APIGateway;
+using Amazon.Lambda.APIGatewayEvents;
+using Amazon.Lambda.Core;
+using System.Collections.Generic;
+
+namespace TestServerlessApp
+{
+ ///
+ /// Example demonstrating Lambda Authorizer annotations
+ ///
+ public class AuthorizerFunctions
+ {
+ ///
+ /// HTTP API Lambda Authorizer - validates tokens and returns user context
+ ///
+ [LambdaFunction(ResourceName = "HttpApiAuthorizer")]
+ [HttpApiAuthorizer(Name = "MyHttpAuthorizer")]
+ public APIGatewayCustomAuthorizerV2SimpleResponse AuthorizeHttpApi(
+ APIGatewayCustomAuthorizerV2Request request,
+ ILambdaContext context)
+ {
+ var token = request.Headers?.GetValueOrDefault("authorization", "");
+
+ if (IsValidToken(token))
+ {
+ return new APIGatewayCustomAuthorizerV2SimpleResponse
+ {
+ IsAuthorized = true,
+ Context = new Dictionary
+ {
+ { "userId", "user-123" },
+ { "email", "user@example.com" },
+ { "role", "admin" }
+ }
+ };
+ }
+
+ return new APIGatewayCustomAuthorizerV2SimpleResponse { IsAuthorized = false };
+ }
+
+ ///
+ /// Protected HTTP API endpoint - requires authorization
+ ///
+ [LambdaFunction(ResourceName = "ProtectedHttpApiFunction")]
+ [HttpApi(LambdaHttpMethod.Get, "/api/protected", Authorizer = "MyHttpAuthorizer")]
+ public object GetProtectedResource(
+ [FromCustomAuthorizer(Name = "userId")] string userId,
+ [FromCustomAuthorizer(Name = "email")] string email)
+ {
+ return new { UserId = userId, Email = email, Message = "This is a protected resource" };
+ }
+
+ ///
+ /// Another protected HTTP API endpoint using the same authorizer
+ ///
+ [LambdaFunction(ResourceName = "AdminHttpApiFunction")]
+ [HttpApi(LambdaHttpMethod.Get, "/api/admin", Authorizer = "MyHttpAuthorizer")]
+ public object AdminEndpoint(
+ [FromCustomAuthorizer(Name = "userId")] string userId,
+ [FromCustomAuthorizer(Name = "role")] string role)
+ {
+ return new { Message = $"Hello admin {userId}!", Role = role };
+ }
+
+ ///
+ /// Public HTTP API endpoint - no authorization required
+ ///
+ [LambdaFunction(ResourceName = "PublicHttpApiFunction")]
+ [HttpApi(LambdaHttpMethod.Get, "/api/public")]
+ public string PublicEndpoint()
+ {
+ return "This is a public endpoint";
+ }
+
+ ///
+ /// REST API Lambda Authorizer with Token type
+ ///
+ [LambdaFunction(ResourceName = "RestApiAuthorizer")]
+ [RestApiAuthorizer(Name = "MyRestAuthorizer", Type = RestApiAuthorizerType.Token)]
+ public APIGatewayCustomAuthorizerResponse AuthorizeRestApi(
+ APIGatewayCustomAuthorizerRequest request,
+ ILambdaContext context)
+ {
+ // Token is directly available for Token-type authorizers
+ var token = request.AuthorizationToken;
+
+ if (IsValidToken(token))
+ {
+ return new APIGatewayCustomAuthorizerResponse
+ {
+ PrincipalID = "user-123",
+ PolicyDocument = new APIGatewayCustomAuthorizerPolicy
+ {
+ Version = "2012-10-17",
+ Statement = new List
+ {
+ new APIGatewayCustomAuthorizerPolicy.IAMPolicyStatement
+ {
+ Effect = "Allow",
+ Action = new HashSet { "execute-api:Invoke" },
+ Resource = new HashSet { request.MethodArn }
+ }
+ }
+ },
+ Context = new APIGatewayCustomAuthorizerContextOutput
+ {
+ ["userId"] = "user-123",
+ ["email"] = "user@example.com"
+ }
+ };
+ }
+
+ // Return Deny policy for invalid tokens
+ return new APIGatewayCustomAuthorizerResponse
+ {
+ PrincipalID = "anonymous",
+ PolicyDocument = new APIGatewayCustomAuthorizerPolicy
+ {
+ Version = "2012-10-17",
+ Statement = new List
+ {
+ new APIGatewayCustomAuthorizerPolicy.IAMPolicyStatement
+ {
+ Effect = "Deny",
+ Action = new HashSet { "execute-api:Invoke" },
+ Resource = new HashSet { request.MethodArn }
+ }
+ }
+ }
+ };
+ }
+
+ ///
+ /// Protected REST API endpoint
+ ///
+ [LambdaFunction(ResourceName = "ProtectedRestApiFunction")]
+ [RestApi(LambdaHttpMethod.Get, "/api/rest/protected", Authorizer = "MyRestAuthorizer")]
+ public object GetRestProtectedResource(
+ [FromCustomAuthorizer(Name = "userId")] string userId)
+ {
+ return new { UserId = userId, Source = "REST API" };
+ }
+
+ ///
+ /// HTTP API Authorizer with custom header and caching
+ ///
+ [LambdaFunction(ResourceName = "ApiKeyAuthorizer")]
+ [HttpApiAuthorizer(
+ Name = "ApiKeyAuth",
+ IdentityHeader = "X-Api-Key",
+ ResultTtlInSeconds = 300)]
+ public APIGatewayCustomAuthorizerV2SimpleResponse ValidateApiKey(
+ APIGatewayCustomAuthorizerV2Request request,
+ ILambdaContext context)
+ {
+ var apiKey = request.Headers?.GetValueOrDefault("x-api-key", "");
+
+ if (!string.IsNullOrEmpty(apiKey) && apiKey.StartsWith("valid-"))
+ {
+ return new APIGatewayCustomAuthorizerV2SimpleResponse
+ {
+ IsAuthorized = true,
+ Context = new Dictionary
+ {
+ { "clientId", "client-456" },
+ { "tier", "premium" }
+ }
+ };
+ }
+
+ return new APIGatewayCustomAuthorizerV2SimpleResponse { IsAuthorized = false };
+ }
+
+ ///
+ /// Endpoint protected by API key authorizer
+ ///
+ [LambdaFunction(ResourceName = "ApiKeyProtectedFunction")]
+ [HttpApi(LambdaHttpMethod.Get, "/api/external", Authorizer = "ApiKeyAuth")]
+ public object ExternalEndpoint(
+ [FromCustomAuthorizer(Name = "clientId")] string clientId,
+ [FromCustomAuthorizer(Name = "tier")] string tier)
+ {
+ return new { ClientId = clientId, Tier = tier };
+ }
+
+ private bool IsValidToken(string token)
+ {
+ return token?.StartsWith("Bearer ") == true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Libraries/test/TestServerlessApp/serverless.template b/Libraries/test/TestServerlessApp/serverless.template
index 0e3befbe1..b238c84ee 100644
--- a/Libraries/test/TestServerlessApp/serverless.template
+++ b/Libraries/test/TestServerlessApp/serverless.template
@@ -1175,6 +1175,331 @@
}
}
}
+ },
+ "HttpApiAuthorizer": {
+ "Type": "AWS::Serverless::Function",
+ "Metadata": {
+ "Tool": "Amazon.Lambda.Annotations"
+ },
+ "Properties": {
+ "Runtime": "dotnet6",
+ "CodeUri": ".",
+ "MemorySize": 512,
+ "Timeout": 30,
+ "Policies": [
+ "AWSLambdaBasicExecutionRole"
+ ],
+ "PackageType": "Zip",
+ "Handler": "TestServerlessApp::TestServerlessApp.AuthorizerFunctions_AuthorizeHttpApi_Generated::AuthorizeHttpApi"
+ }
+ },
+ "ProtectedHttpApiFunction": {
+ "Type": "AWS::Serverless::Function",
+ "Metadata": {
+ "Tool": "Amazon.Lambda.Annotations",
+ "SyncedEvents": [
+ "RootGet"
+ ],
+ "SyncedEventProperties": {
+ "RootGet": [
+ "Path",
+ "Method",
+ "Auth.Authorizer",
+ "ApiId.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": "MyHttpAuthorizer"
+ },
+ "ApiId": {
+ "Ref": "CustomHttpApi"
+ }
+ }
+ }
+ }
+ }
+ },
+ "AdminHttpApiFunction": {
+ "Type": "AWS::Serverless::Function",
+ "Metadata": {
+ "Tool": "Amazon.Lambda.Annotations",
+ "SyncedEvents": [
+ "RootGet"
+ ],
+ "SyncedEventProperties": {
+ "RootGet": [
+ "Path",
+ "Method",
+ "Auth.Authorizer",
+ "ApiId.Ref"
+ ]
+ }
+ },
+ "Properties": {
+ "Runtime": "dotnet6",
+ "CodeUri": ".",
+ "MemorySize": 512,
+ "Timeout": 30,
+ "Policies": [
+ "AWSLambdaBasicExecutionRole"
+ ],
+ "PackageType": "Zip",
+ "Handler": "TestServerlessApp::TestServerlessApp.AuthorizerFunctions_AdminEndpoint_Generated::AdminEndpoint",
+ "Events": {
+ "RootGet": {
+ "Type": "HttpApi",
+ "Properties": {
+ "Path": "/api/admin",
+ "Method": "GET",
+ "Auth": {
+ "Authorizer": "MyHttpAuthorizer"
+ },
+ "ApiId": {
+ "Ref": "CustomHttpApi"
+ }
+ }
+ }
+ }
+ }
+ },
+ "PublicHttpApiFunction": {
+ "Type": "AWS::Serverless::Function",
+ "Metadata": {
+ "Tool": "Amazon.Lambda.Annotations",
+ "SyncedEvents": [
+ "RootGet"
+ ],
+ "SyncedEventProperties": {
+ "RootGet": [
+ "Path",
+ "Method"
+ ]
+ }
+ },
+ "Properties": {
+ "Runtime": "dotnet6",
+ "CodeUri": ".",
+ "MemorySize": 512,
+ "Timeout": 30,
+ "Policies": [
+ "AWSLambdaBasicExecutionRole"
+ ],
+ "PackageType": "Zip",
+ "Handler": "TestServerlessApp::TestServerlessApp.AuthorizerFunctions_PublicEndpoint_Generated::PublicEndpoint",
+ "Events": {
+ "RootGet": {
+ "Type": "HttpApi",
+ "Properties": {
+ "Path": "/api/public",
+ "Method": "GET"
+ }
+ }
+ }
+ }
+ },
+ "RestApiAuthorizer": {
+ "Type": "AWS::Serverless::Function",
+ "Metadata": {
+ "Tool": "Amazon.Lambda.Annotations"
+ },
+ "Properties": {
+ "Runtime": "dotnet6",
+ "CodeUri": ".",
+ "MemorySize": 512,
+ "Timeout": 30,
+ "Policies": [
+ "AWSLambdaBasicExecutionRole"
+ ],
+ "PackageType": "Zip",
+ "Handler": "TestServerlessApp::TestServerlessApp.AuthorizerFunctions_AuthorizeRestApi_Generated::AuthorizeRestApi"
+ }
+ },
+ "ProtectedRestApiFunction": {
+ "Type": "AWS::Serverless::Function",
+ "Metadata": {
+ "Tool": "Amazon.Lambda.Annotations",
+ "SyncedEvents": [
+ "RootGet"
+ ],
+ "SyncedEventProperties": {
+ "RootGet": [
+ "Path",
+ "Method",
+ "Auth.Authorizer",
+ "RestApiId.Ref"
+ ]
+ }
+ },
+ "Properties": {
+ "Runtime": "dotnet6",
+ "CodeUri": ".",
+ "MemorySize": 512,
+ "Timeout": 30,
+ "Policies": [
+ "AWSLambdaBasicExecutionRole"
+ ],
+ "PackageType": "Zip",
+ "Handler": "TestServerlessApp::TestServerlessApp.AuthorizerFunctions_GetRestProtectedResource_Generated::GetRestProtectedResource",
+ "Events": {
+ "RootGet": {
+ "Type": "Api",
+ "Properties": {
+ "Path": "/api/rest/protected",
+ "Method": "GET",
+ "Auth": {
+ "Authorizer": "MyRestAuthorizer"
+ },
+ "RestApiId": {
+ "Ref": "CustomRestApi"
+ }
+ }
+ }
+ }
+ }
+ },
+ "ApiKeyAuthorizer": {
+ "Type": "AWS::Serverless::Function",
+ "Metadata": {
+ "Tool": "Amazon.Lambda.Annotations"
+ },
+ "Properties": {
+ "Runtime": "dotnet6",
+ "CodeUri": ".",
+ "MemorySize": 512,
+ "Timeout": 30,
+ "Policies": [
+ "AWSLambdaBasicExecutionRole"
+ ],
+ "PackageType": "Zip",
+ "Handler": "TestServerlessApp::TestServerlessApp.AuthorizerFunctions_ValidateApiKey_Generated::ValidateApiKey"
+ }
+ },
+ "ApiKeyProtectedFunction": {
+ "Type": "AWS::Serverless::Function",
+ "Metadata": {
+ "Tool": "Amazon.Lambda.Annotations",
+ "SyncedEvents": [
+ "RootGet"
+ ],
+ "SyncedEventProperties": {
+ "RootGet": [
+ "Path",
+ "Method",
+ "Auth.Authorizer",
+ "ApiId.Ref"
+ ]
+ }
+ },
+ "Properties": {
+ "Runtime": "dotnet6",
+ "CodeUri": ".",
+ "MemorySize": 512,
+ "Timeout": 30,
+ "Policies": [
+ "AWSLambdaBasicExecutionRole"
+ ],
+ "PackageType": "Zip",
+ "Handler": "TestServerlessApp::TestServerlessApp.AuthorizerFunctions_ExternalEndpoint_Generated::ExternalEndpoint",
+ "Events": {
+ "RootGet": {
+ "Type": "HttpApi",
+ "Properties": {
+ "Path": "/api/external",
+ "Method": "GET",
+ "Auth": {
+ "Authorizer": "ApiKeyAuth"
+ },
+ "ApiId": {
+ "Ref": "CustomHttpApi"
+ }
+ }
+ }
+ }
+ }
+ },
+ "CustomHttpApi": {
+ "Type": "AWS::Serverless::HttpApi",
+ "Metadata": {
+ "Tool": "Amazon.Lambda.Annotations"
+ },
+ "Properties": {
+ "Auth": {
+ "Authorizers": {
+ "MyHttpAuthorizer": {
+ "FunctionArn": {
+ "Fn::GetAtt": [
+ "HttpApiAuthorizer",
+ "Arn"
+ ]
+ },
+ "AuthorizerPayloadFormatVersion": "2.0",
+ "EnableSimpleResponses": true,
+ "Identity": {
+ "Headers": [
+ "Authorization"
+ ]
+ }
+ },
+ "ApiKeyAuth": {
+ "FunctionArn": {
+ "Fn::GetAtt": [
+ "ApiKeyAuthorizer",
+ "Arn"
+ ]
+ },
+ "AuthorizerPayloadFormatVersion": "2.0",
+ "EnableSimpleResponses": true,
+ "Identity": {
+ "Headers": [
+ "X-Api-Key"
+ ]
+ }
+ }
+ }
+ }
+ }
+ },
+ "CustomRestApi": {
+ "Type": "AWS::Serverless::Api",
+ "Properties": {
+ "StageName": "Prod",
+ "Auth": {
+ "Authorizers": {
+ "MyRestAuthorizer": {
+ "FunctionArn": {
+ "Fn::GetAtt": [
+ "RestApiAuthorizer",
+ "Arn"
+ ]
+ },
+ "Identity": {
+ "Header": "Authorization"
+ },
+ "FunctionPayloadType": "TOKEN"
+ }
+ }
+ }
+ },
+ "Metadata": {
+ "Tool": "Amazon.Lambda.Annotations"
+ }
}
},
"Outputs": {