Skip to content

Conversation

@kabaluk
Copy link
Contributor

@kabaluk kabaluk commented Mar 26, 2023

PR Description: [FromCustomAuthorizer] Attribute Support

Summary

Adds [FromCustomAuthorizer] attribute to extract Lambda authorizer context values directly into method parameters.

Usage

[HttpApi(HttpMethod.Get, "/api/user-info")]
public IHttpResult GetUserInfo(
    [FromCustomAuthorizer(Name = "userId")] string userId,
    [FromCustomAuthorizer(Name = "email")] string email)

Supported API Types

API Type Context Path
REST API RequestContext.Authorizer["key"]
HTTP API v1 RequestContext.Authorizer["key"]
HTTP API v2 RequestContext.Authorizer.Lambda["key"]

Behavior

  • Key name: Uses Name property if specified, otherwise falls back to parameter name
  • Returns 401 Unauthorized when: authorizer context is null, key not found, or type conversion fails
  • Supports IHttpResult return types

Testing

  • Unit tests with snapshot verification
  • Integration tests deploy real Lambda functions to AWS and verify end-to-end flow with custom authorizers

Copy link
Member

@normj normj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool PR!

Can you fix the whitespaces changes to the LambdaFunctionTemplate.tt file? The whole file is looks different.

Who should we handle the other type of authorizer properties. For example on the APIGatewayHttpApiV2ProxyRequest besides the Lambda property there is also Jwt and IAM properties. Should we make the string accessing the authorizer field have the prefix of the property.

@kabaluk
Copy link
Contributor Author

kabaluk commented Mar 29, 2023

Wiil do my best with the withspace.
The other types of authorizer are not custom authorizers as far as i am aware.
I would not create string prefixes.
We could create an iam authorizer attribute and a jwt authorizer attribute and those wold look on the respective locations...

@kabaluk
Copy link
Contributor Author

kabaluk commented Mar 30, 2023

don't seem to be able to fix the whitespace issue to save my life :(
Please review ignoring whitespace ?? Maybe?

Tried setting EOL to LF and CR, both cause problems with the generated file. The correct one seems to be CRLF .
but obviously git doens't like it :(

Please feel free to edit.

@kabaluk kabaluk requested a review from normj March 30, 2023 01:09
@kabaluk
Copy link
Contributor Author

kabaluk commented May 4, 2023

Would be really nice to get this avail;able. I will gladly fix the conflicts if this can be looked at again.

@kabaluk
Copy link
Contributor Author

kabaluk commented May 8, 2023

I can see you did a major refactoring in the dev branch will push again once released.

If i might be so bold, I looked at what you did.
why not take it one step further and separate each attribute into it's own file?
Talking about the APIGatewaySetupParameters.tt
Breaking that file down, using the technique you used before, would make it much easier to add new attributes in the future

Just a thought.

@normj
Copy link
Member

normj commented May 9, 2023

@kabaluk I suspect adding new attributes would be done in separate files. For example if we added SQS or S3 I would put them in separate TT. One of my goals with the refactor was to make it easier to add new attributes as the previous monolithic tt file was really hard to reconcile with. HttpApi and RestApi attributes are 9X percent the same code so I didn't really want to make 2 separate tt files for each one of those. Plus I thought the parameter setup was getting pretty long so I was looking for some separation which is why I divided the parameter setup and the invoke. I suspect in SQS or S3 use cases the parameter setup wouldn't be so complicated.

Another goal I had was I wanted to generate the exact same code including whitespaces to make the PR review easier and then later I could do more specific refactoring. Otherwise the PR review would have been every line is different and that is hard to review. But going forward I could see putting each of the FromXXX in a separate tt file as well.

@kabaluk
Copy link
Contributor Author

kabaluk commented May 9, 2023

@kabaluk I suspect adding new attributes would be done in separate files. For example if we added SQS or S3 I would put them in separate TT. One of my goals with the refactor was to make it easier to add new attributes as the previous monolithic tt file was really hard to reconcile with. HttpApi and RestApi attributes are 9X percent the same code so I didn't really want to make 2 separate tt files for each one of those. Plus I thought the parameter setup was getting pretty long so I was looking for some separation which is why I divided the parameter setup and the invoke. I suspect in SQS or S3 use cases the parameter setup wouldn't be so complicated.

Another goal I had was I wanted to generate the exact same code including whitespaces to make the PR review easier and then later I could do more specific refactoring. Otherwise the PR review would have been every line is different and that is hard to review. But going forward I could see putting each of the FromXXX in a separate tt file as well.

I was talking specifically of the FromXXX attributes. Apologies, I should have been more explicit. Sounds like a great change and it would make it a lot easier to add and test more FromXXX attributes. Looking forward to have that in main so i can re add the FromCustomAuthorizer attribute.
Even started looking on how to do the FromJwt. 😄

@kabaluk
Copy link
Contributor Author

kabaluk commented May 11, 2023

Readded CustomAuthorizerAttribute..

Hope you like it :)

@kabaluk
Copy link
Contributor Author

kabaluk commented May 18, 2023

Any possibility of having this looked at again, please?

Copy link
Member

@normj normj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did an initial scan and it looks good. I had one comment so far. I need to test the experience end to end next. I'll try and find time to do that soon.

@ashishdhingra
Copy link
Contributor

@kabaluk Please review the last review comment from @normj and see if you are able to address it.

@ashishdhingra ashishdhingra added module/aspnetcore-support p2 This is a standard priority issue feature-request A feature should be added or improved. queued labels Sep 1, 2023
@kabaluk
Copy link
Contributor Author

kabaluk commented Sep 3, 2023

@kabaluk Please review the last review comment from @normj and see if you are able to address it.

Please have a look.

@kabaluk kabaluk requested review from normj and philasmar September 3, 2023 20:26
@GarrettBeatty GarrettBeatty changed the base branch from master to dev August 21, 2025 15:24
@GarrettBeatty GarrettBeatty marked this pull request as draft January 28, 2026 16:51
@GarrettBeatty
Copy link
Contributor

im working on rebasing this

@GarrettBeatty GarrettBeatty force-pushed the add-from-custom-authorizer-attribute branch from f14bd0b to 48afb69 Compare January 28, 2026 17:51
try
{
var __authValue_<#= parameter.Name #>__ = __request__.RequestContext.Authorizer["<#= authKey #>"];
<#= parameter.Name #> = (<#= parameter.Type.FullName #>)Convert.ChangeType(__authValue_<#= parameter.Name #>__?.ToString(), typeof(<#= parameter.Type.FullNameWithoutAnnotations #>));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why We Need .ToString() for Custom Authorizer Values

The Data Flow

  1. Authorizer Lambda returns context: When your custom authorizer runs, it returns a context dictionary:

    Context = new Dictionary<string, object>
    {
        { "userId", "12345" },
        { "permissions", "admin" }
    }
  2. API Gateway passes this to the protected Lambda in the request payload as JSON:

    {
      "requestContext": {
        "authorizer": {
          "userId": "12345",
          "permissions": "admin"
        }
      }
    }
  3. Lambda deserializes the request using the configured serializer (System.Text.Json or Newtonsoft.Json).

The Problem: Dictionary<string, object> Deserialization

The Authorizer property is typed as Dictionary<string, object>:

public class APIGatewayCustomAuthorizerContext : Dictionary<string, object>

When the serializer encounters a JSON value like "12345" and needs to deserialize it into object, it doesn't know what concrete type to use. So:

  • System.Text.Json wraps it in a JsonElement struct
  • Newtonsoft.Json wraps it in a JToken (like JValue)

so rather than having statements like


if (__authValue__ is System.Text.Json.JsonElement jsonElement)
{
    userId = Convert.ChangeType(jsonElement.ToString(), typeof(string));
}

i just call toString which both of these serializers have.

not sure if there is a better way

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@normj not sure if you have any better ideas here of it ToString is good enough

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the values be numerics instead of strings? If so how would that work out with this approach? Or are we saying for the FromCustomerAuthorizer we only support string parameters. If so we should make sure we are enforcing that and put out diagnostic warnings if they attempt something we don't support.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so what im doing here is converting it to a string then using https://learn.microsoft.com/en-us/dotnet/api/system.convert.changetype?view=net-10.0 afterwards to convert to the parameters type.

i will add an integration test to confirm

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so i added integration tests to confirm

  1. int values work
  2. string values work
  3. boolean values work

double values do not work because of a limitation with api gateway not supporting it.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 28 out of 28 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 31 out of 31 changed files in this pull request and generated 8 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@GarrettBeatty
Copy link
Contributor

@GarrettBeatty GarrettBeatty marked this pull request as ready for review February 2, 2026 19:40
using Amazon.CloudFormation;
using Amazon.CloudFormation.Model;

namespace IntegrationTests.Helpers
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is all existing code just moved into a common project to share between the two integration test projects

try
{
var __authValue_<#= parameter.Name #>__ = __request__.RequestContext.Authorizer["<#= authKey #>"];
<#= parameter.Name #> = (<#= parameter.Type.FullName #>)Convert.ChangeType(__authValue_<#= parameter.Name #>__?.ToString(), typeof(<#= parameter.Type.FullNameWithoutAnnotations #>));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can the values be numerics instead of strings? If so how would that work out with this approach? Or are we saying for the FromCustomerAuthorizer we only support string parameters. If so we should make sure we are enforcing that and put out diagnostic warnings if they attempt something we don't support.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 56 out of 56 changed files in this pull request and generated 42 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1444 to +1447
{
var expectedTemplateContent = await ReadSnapshotContent(Path.Combine("Snapshots", "ServerlessTemplates", "authorizerHttpApi.template"));
var expectedRestAuthorizerGenerated = await ReadSnapshotContent(Path.Combine("Snapshots", "CustomAuthorizerHttpApiExample_HttpApiAuthorizer_Generated.g.cs"));

Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable name expectedRestAuthorizerGenerated is misleading in this HTTP API test (it contains the expected HttpApi generated output). Rename it to something like expectedHttpApiAuthorizerGenerated to avoid confusion and make future maintenance easier.

Copilot uses AI. Check for mistakes.
Comment on lines +116 to +117
# Response: "Hello user-12345! You are a admin in tenant 42."
```
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor grammar in the example response: "You are a admin" should be "You are an admin".

Copilot uses AI. Check for mistakes.
public async Task HttpApiV1UserInfo_WithValidAuth_ReturnsAuthorizerContext()
{
// Arrange
var request = new HttpRequestMessage(HttpMethod.Get, $"{_fixture.HttpApiUrl}/api/http-v1-user-info");
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disposable 'HttpRequestMessage' is created but not disposed.

Copilot uses AI. Check for mistakes.
public async Task HttpApiV1UserInfo_WithMissingAuthorizerContextKey_ReturnsUnauthorized()
{
// Arrange - use partial-context token that authorizes but omits expected context keys
var request = new HttpRequestMessage(HttpMethod.Get, $"{_fixture.HttpApiUrl}/api/http-v1-user-info");
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disposable 'HttpRequestMessage' is created but not disposed.

Copilot uses AI. Check for mistakes.
#if NET6_0_OR_GREATER
__context__.Logger.LogError(e, "Failed to convert authorizer attribute 'theAuthKey', returning unauthorized.");
#else
__context__.Logger.Log("Failed to convert authorizer attribute 'theAuthKey', returning unauthorized. Exception: " + e.ToString());
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant call to 'ToString' on a String object.

Copilot uses AI. Check for mistakes.
if (val is System.Text.Json.JsonElement jsonElement)
{
context.Logger.LogLine($" JsonElement.ValueKind: {jsonElement.ValueKind}");
context.Logger.LogLine($" JsonElement.ToString(): '{jsonElement.ToString()}'");
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant call to 'ToString' on a String object.

Copilot uses AI. Check for mistakes.
// Try to get context values if Lambda dictionary exists
if (request.RequestContext.Authorizer.Lambda != null)
{
var userId = request.RequestContext.Authorizer.Lambda.ContainsKey("userId")
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inefficient use of 'ContainsKey' and indexer.

Copilot uses AI. Check for mistakes.
var userId = request.RequestContext.Authorizer.Lambda.ContainsKey("userId")
? request.RequestContext.Authorizer.Lambda["userId"]?.ToString()
: "NOT_FOUND";
var tenantId = request.RequestContext.Authorizer.Lambda.ContainsKey("tenantId")
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inefficient use of 'ContainsKey' and indexer.

Copilot uses AI. Check for mistakes.
var tenantId = request.RequestContext.Authorizer.Lambda.ContainsKey("tenantId")
? request.RequestContext.Authorizer.Lambda["tenantId"]?.ToString()
: "NOT_FOUND";
var userRole = request.RequestContext.Authorizer.Lambda.ContainsKey("userRole")
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inefficient use of 'ContainsKey' and indexer.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature-request A feature should be added or improved. module/aspnetcore-support p2 This is a standard priority issue queued

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants