Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wrong response type returned for Custom Authorizer using Minimal API #1666

Closed
vladyslav-sosnov opened this issue Jan 29, 2024 · 5 comments
Closed
Assignees
Labels
bug This issue is a bug. closed-for-staleness module/aspnetcore-support needs-reproduction This issue needs reproduction. response-requested Waiting on additional info and feedback. Will move to close soon in 7 days.

Comments

@vladyslav-sosnov
Copy link

Describe the bug

Custom Authorizers return AWS Lambda proxy integration response using Minimal API approach instead of API Gateway Lambda authorizer response
For comparison, the Function Handler approach returns the correct response type, however, can not inject HttpContext (Raised separate issue)

Expected Behavior

API Gateway Lambda Custom authorizer response should return a 'API Gateway Lambda authorizer response' when using Minimal API.

Example of EXPECTED response

{
  "principalId": "12345",
  "policyDocument": {
	"Version": "2012-10-17",
	"Statement": [
	  {
		"Action": "execute-api:Invoke",
		"Effect": "Allow|Deny",
		"Resource": "arn:aws:execute-api:{regionId}:{accountId}:{apiId}/{stage}/{httpVerb}/[{resource}/[{child-resources}]]"
	  }
	]
  },
  "context": {
	"stringKey": "value",
	"numberKey": "1",
	"booleanKey": "true"
  },
  "usageIdentifierKey": "{api-key}"
}

Current Behavior

API Gateway Lambda Custom authorizer returns a AWS Lambda proxy integration response when using Minimal API. The expected payload is wrapped in the body property.

Example of ACTUAL response

{
	"isBase64Encoded": false,
	"statusCode": 200,
	"headers": { "headerName": "headerValue", ... },
	"multiValueHeaders": { "headerName": ["headerValue", "headerValue2", ...], ... },
	"body": "... data from response in a string format ..."
}

Reproduction Steps

Code sample illustrating the return of an incorrect response.

using Amazon.Lambda.APIGatewayEvents;
using Amazon.Lambda.AspNetCoreServer;
using Microsoft.AspNetCore.DataProtection;

var authorizerSettings = new SessionAuthorizerSettings();
var builder = WebApplication.CreateBuilder(args);

var services = builder.Services;

services.AddAWSLambdaHosting(LambdaEventSource.HttpApi);

services.AddSingleton<ISessionAuthorizerSettings, SessionAuthorizerSettings>();

services
    .AddDataProtection()
    .SetApplicationName(authorizerSettings.DataProtectionAppName)
    .DisableAutomaticKeyGeneration();

services
    .AddHttpContextAccessor();

services
    .AddAWSDynamoDBDistributedCache(options =>
    {
        options.TableName = authorizerSettings.CacheTableName;
        options.PartitionKeyName = authorizerSettings.PartitionKeyName;
        options.TTLAttributeName = authorizerSettings.TTLAttributeName;
        options.UseConsistentReads = true;
    })
    .AddSession(options =>
    {
        options.Cookie.Name = ".Cookie.Session";
        options.IdleTimeout = TimeSpan.FromMinutes(30);
        options.Cookie.HttpOnly = true;
        options.Cookie.IsEssential = true;
    });

var app = builder.BuildApp();

app.UseSession();

app.Map("/{*invokedRoute}", (HttpContext httpContext) =>
{
    var request = httpContext.Items[AbstractAspNetCoreFunction.LAMBDA_REQUEST_OBJECT] as APIGatewayHttpApiV2ProxyRequest;
    var lambdaContext = _httpContext.Items[AbstractAspNetCoreFunction.LAMBDA_CONTEXT] as ILambdaContext;

    var userId = "12345";

    // HttpContext is populated and the session data can be accessed.
    var session = httpContext.Session;
    var sessionData = ObjectSerializer.Deserialize<SessionPermissions>(session.GetString(userId));

    // An example of a typical response from the authorizer to invoke the requested Lambda.
    var response = new APIGatewayCustomAuthorizerResponse
    {
        PolicyDocument = new APIGatewayCustomAuthorizerPolicy
        {
            Version = "2012-10-17",
            Statement = new List<APIGatewayCustomAuthorizerPolicy.IAMPolicyStatement>
            {
                 new()
                 {
                     Action = new HashSet<string> {"execute-api:Invoke"},
                     Effect = "Allow",
                     Resource = new HashSet<string> { $"arn:aws:execute-api:{lambdaContext.InvokedFunctionArn}:{request.RequestContext.AccountId}:{request.RequestContext.ApiId}/$default/*" }
                 }
            },
        },
        PrincipalID = userId,
        Context = new APIGatewayCustomAuthorizerContextOutput
        {
            ["data"] = sessionData.Data
        }
    };

    // ISSUE: Returns AWS Lambda proxy integration response with the APIGatewayCustomAuthorizerResponse in a body instead.
    return response;
});

await app.RunAsync();

Possible Solution

No response

Additional Information/Context

Alternatively, a Function Handler (using the Top-Level statements) approach was trialed where the response type was correct, however, the HttpContext was null.

AWS .NET SDK and/or Package version used

  • Amazon.Lambda.AspNetCoreServer.Hosting, Version="1.6.1"
  • AWS.AspNetCore.DistributedCacheProvider, Version="0.9.2"
  • AWSSDK.S3, Version="3.7.103.37"

Targeted .NET Platform

.Net 6

Operating System and version

AWS Lambda

@vladyslav-sosnov vladyslav-sosnov added bug This issue is a bug. needs-triage This issue or PR still needs to be triaged. labels Jan 29, 2024
@bhoradc bhoradc added needs-reproduction This issue needs reproduction. module/aspnetcore-support and removed needs-triage This issue or PR still needs to be triaged. labels Mar 5, 2024
@ashishdhingra
Copy link
Contributor

ashishdhingra commented Mar 8, 2024

@vladyslav-sosnov Good afternoon. Please refer #1527 (reply in thread). The response type appears to be correct based on below findings:

What does your Function Handler (using the Top-Level statements) looks like? Per other issue #1665, I guess your handler looks like this in your top level statements:

var handler = (APIGatewayHttpApiV2ProxyRequest request, ILambdaContext lambdaContext) =>
{
    var userId = "12345";

    // ISSUE: 'httpContext' is always null and therefore, the session can not be accessed.
    var session = httpContext.Session;
    var sessionData = ObjectSerializer.Deserialize<SessionPermissions>(session.GetString(userId));

    // An example of a typical response from the authorizer to invoke the requested Lambda.
    var response = new APIGatewayCustomAuthorizerResponse
    {
        PolicyDocument = new APIGatewayCustomAuthorizerPolicy
        {
            Version = "2012-10-17",
            Statement = new List<APIGatewayCustomAuthorizerPolicy.IAMPolicyStatement>
            {
                 new()
                 {
                     Action = new HashSet<string> {"execute-api:Invoke"},
                     Effect = "Allow",
                     Resource = new HashSet<string> { $"arn:aws:execute-api:{lambdaContext.InvokedFunctionArn}:{request.RequestContext.AccountId}:{request.RequestContext.ApiId}/$default/*" }
                 }
            },
        },
        PrincipalID = userId,
        Context = new APIGatewayCustomAuthorizerContextOutput
        {
            ["data"] = sessionData.Data
        }
    };
};

Here, the Lambda expression var handler = (APIGatewayHttpApiV2ProxyRequest request, ILambdaContext lambdaContext) => {} specifies the data type of input parameters. The return type is inferred from the return statement (which is missing from above block; I guess you intended to return response), which in this case is APIGatewayCustomAuthorizerResponse and hence works for your use case.

CCing @normj for additional inputs.

Thanks,
Ashish

@ashishdhingra ashishdhingra added the response-requested Waiting on additional info and feedback. Will move to close soon in 7 days. label Mar 8, 2024
@vladyslav-sosnov
Copy link
Author

@ashishdhingra
CC: @normj

The Function Handler (using the Top-Level statements) approach can not be used as per this comment in the code:

// ISSUE: 'httpContext' is always null and therefore, the session can not be accessed.
var session = httpContext.Session;
var sessionData = ObjectSerializer.Deserialize<SessionPermissions>(session.GetString(userId));

That is why a separate issue #1665 was raised.

For the current (Minimal API) approach, as described in this issue, another type is returned.

@github-actions github-actions bot removed the response-requested Waiting on additional info and feedback. Will move to close soon in 7 days. label Mar 13, 2024
@ashishdhingra
Copy link
Contributor

@ashishdhingra CC: @normj

The Function Handler (using the Top-Level statements) approach can not be used as per this comment in the code:

// ISSUE: 'httpContext' is always null and therefore, the session can not be accessed.
var session = httpContext.Session;
var sessionData = ObjectSerializer.Deserialize<SessionPermissions>(session.GetString(userId));

That is why a separate issue #1665 was raised.

For the current (Minimal API) approach, as described in this issue, another type is returned.

@vladyslav-sosnov Per analysis in #1666 (comment), it is returning the correct type. I'm unsure if it's possible to return different type in case of minimal API. This needs to be discussed with the team.

@normj
Copy link
Member

normj commented Apr 20, 2024

I had never thought of using an ASP.NET Core Lambda function as the implementer of an API Gateway authorizer. The bridge library assumes the incoming event is either APIGatewayHttpApiV2ProxyRequest or APIGatewayProxyRequest depending on how the function is configured. These types are close but not the same as APIGatewayCustomAuthorizerV2Request or APIGatewayCustomAuthorizerRequest. Looks like you got partially luckily that you didn't need all of the fields in the authorizer request but some get dropped like IdentitySource.

The library is designed where there is a separate subclass for request/response pairing. For example when using API Gateway HTTP V2 as the event source the APIGatewayHttpApiV2ProxyFunction subclass is used for handling marshalling and unmarshalling for APIGatewayHttpApiV2ProxyRequest and APIGatewayHttpApiV2ProxyResponse. To get the authorizer to work we would need to create a separate subclass for the authorizer. But that gets messier because I imagine you would want to have a mix of endpoints for authorizers and application endpoints and that would require the use of 2 subclasses. Not sure how that would fit in here.

The TLDR here is this would require substantial architecture changes that I'm not really comfortable making. Unless not till we have more asks for this type of feature.

Seems like your reasoning for needing this to be part of an ASP.NET Core Lambda function is access to session data. Since you can't use an in memory session store where are you storing session data? Is it possible to right a light weight Lambda function without ASP.NET Core that can access the session data and then you have control over the response format?

@ashishdhingra ashishdhingra added response-requested Waiting on additional info and feedback. Will move to close soon in 7 days. and removed needs-review labels Apr 26, 2024
Copy link
Contributor

github-actions bot commented May 1, 2024

This issue has not received a response in 5 days. If you want to keep this issue open, please just leave a comment below and auto-close will be canceled.

@github-actions github-actions bot added closing-soon This issue will automatically close in 4 days unless further comments are made. closed-for-staleness and removed closing-soon This issue will automatically close in 4 days unless further comments are made. labels May 1, 2024
@github-actions github-actions bot closed this as completed May 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug This issue is a bug. closed-for-staleness module/aspnetcore-support needs-reproduction This issue needs reproduction. response-requested Waiting on additional info and feedback. Will move to close soon in 7 days.
Projects
None yet
Development

No branches or pull requests

4 participants