-
Notifications
You must be signed in to change notification settings - Fork 113
[examples] Add an example of an APIGateway Lambda Authorizer #453
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
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
samconfig.toml | ||
Makefile |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
// swift-tools-version:6.0 | ||
|
||
import PackageDescription | ||
|
||
// needed for CI to test the local version of the library | ||
import struct Foundation.URL | ||
|
||
#if os(macOS) | ||
let platforms: [PackageDescription.SupportedPlatform]? = [.macOS(.v15)] | ||
#else | ||
let platforms: [PackageDescription.SupportedPlatform]? = nil | ||
#endif | ||
|
||
let package = Package( | ||
name: "swift-aws-lambda-runtime-example", | ||
platforms: [.macOS(.v15)], | ||
products: [ | ||
.executable(name: "APIGatewayLambda", targets: ["APIGatewayLambda"]), | ||
.executable(name: "AuthorizerLambda", targets: ["AuthorizerLambda"]), | ||
], | ||
dependencies: [ | ||
// during CI, the dependency on local version of swift-aws-lambda-runtime is added dynamically below | ||
.package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", branch: "main"), | ||
.package(url: "https://github.com/swift-server/swift-aws-lambda-events.git", branch: "main"), | ||
], | ||
targets: [ | ||
.executableTarget( | ||
name: "APIGatewayLambda", | ||
dependencies: [ | ||
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"), | ||
.product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"), | ||
] | ||
), | ||
.executableTarget( | ||
name: "AuthorizerLambda", | ||
dependencies: [ | ||
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"), | ||
.product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"), | ||
] | ||
), | ||
] | ||
) | ||
|
||
if let localDepsPath = Context.environment["LAMBDA_USE_LOCAL_DEPS"], | ||
localDepsPath != "", | ||
let v = try? URL(fileURLWithPath: localDepsPath).resourceValues(forKeys: [.isDirectoryKey]), | ||
v.isDirectory == true | ||
{ | ||
// when we use the local runtime as deps, let's remove the dependency added above | ||
let indexToRemove = package.dependencies.firstIndex { dependency in | ||
if case .sourceControl( | ||
name: _, | ||
location: "https://github.com/swift-server/swift-aws-lambda-runtime.git", | ||
requirement: _ | ||
) = dependency.kind { | ||
return true | ||
} | ||
return false | ||
} | ||
if let indexToRemove { | ||
package.dependencies.remove(at: indexToRemove) | ||
} | ||
|
||
// then we add the dependency on LAMBDA_USE_LOCAL_DEPS' path (typically ../..) | ||
print("[INFO] Compiling against swift-aws-lambda-runtime located at \(localDepsPath)") | ||
package.dependencies += [ | ||
.package(name: "swift-aws-lambda-runtime", path: localDepsPath) | ||
] | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
# Lambda Authorizer with API Gateway | ||
|
||
This is an example of a Lambda Authorizer function. There are two Lambda functions in this example. The first one is the authorizer function. The second one is the business function. The business function is exposed through a REST API using the API Gateway. The API Gateway is configured to use the authorizer function to implement a custom logic to authorize the requests. | ||
|
||
>[!NOTE] | ||
> If your application is protected by JWT tokens, it's recommended to use [the native JWT authorizer provided by the API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-jwt-authorizer.html). The Lambda authorizer is useful when you need to implement a custom authorization logic. See the [OAuth 2.0/JWT authorizer example for AWS SAM](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-controlling-access-to-apis-oauth2-authorizer.html) to learn how to use the native JWT authorizer with SAM. | ||
|
||
## Code | ||
|
||
The authorizer function is a simple function that checks data received from the API Gateway. In this example, the API Gateway is configured to pass the content of the `Authorization` header to the authorizer Lambda function. | ||
|
||
There are two possible responses from a Lambda Authorizer function: policy and simple. The policy response returns an IAM policy document that describes the permissions of the caller. The simple response returns a boolean value that indicates if the caller is authorized or not. You can read more about the two types of responses in the [Lambda authorizer response format](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-lambda-authorizer.html) section of the API Gateway documentation. | ||
|
||
This example uses an authorizer that returns the simple response. The authorizer function is defined in the `Sources/AuthorizerLambda` directory. The business function is defined in the `Sources/APIGatewayLambda` directory. | ||
|
||
## Build & Package | ||
|
||
To build the package, type the following commands. | ||
|
||
```bash | ||
swift build | ||
swift package archive --allow-network-connections docker | ||
``` | ||
|
||
If there is no error, there are two ZIP files ready to deploy, one for the authorizer function and one for the business function. | ||
The ZIP file are located under `.build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager` | ||
|
||
## Deploy | ||
|
||
The deployment must include the Lambda functions and the API Gateway. We use the [Serverless Application Model (SAM)](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html) to deploy the infrastructure. | ||
|
||
**Prerequisites** : Install the [SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html) | ||
|
||
The example directory contains a file named `template.yaml` that describes the deployment. | ||
|
||
To actually deploy your Lambda function and create the infrastructure, type the following `sam` command. | ||
|
||
```bash | ||
sam deploy \ | ||
--resolve-s3 \ | ||
--template-file template.yaml \ | ||
--stack-name APIGatewayWithLambdaAuthorizer \ | ||
--capabilities CAPABILITY_IAM | ||
``` | ||
|
||
At the end of the deployment, the script lists the API Gateway endpoint. | ||
The output is similar to this one. | ||
|
||
``` | ||
----------------------------------------------------------------------------------------------------------------------------- | ||
Outputs | ||
----------------------------------------------------------------------------------------------------------------------------- | ||
Key APIGatewayEndpoint | ||
Description API Gateway endpoint URI | ||
Value https://a5q74es3k2.execute-api.us-east-1.amazonaws.com/demo | ||
----------------------------------------------------------------------------------------------------------------------------- | ||
``` | ||
|
||
## Invoke your Lambda function | ||
|
||
To invoke the Lambda function, use this `curl` command line. Be sure to replace the URL with the API Gateway endpoint returned in the previous step. | ||
|
||
When invoking the Lambda function without `Authorization` header, the response is a `401 Unauthorized` error. | ||
|
||
```bash | ||
curl -v https://a5q74es3k2.execute-api.us-east-1.amazonaws.com/demo | ||
... | ||
> GET /demo HTTP/2 | ||
> Host: 6sm6270j21.execute-api.us-east-1.amazonaws.com | ||
> User-Agent: curl/8.7.1 | ||
> Accept: */* | ||
> | ||
* Request completely sent off | ||
< HTTP/2 401 | ||
< date: Sat, 04 Jan 2025 14:03:02 GMT | ||
< content-type: application/json | ||
< content-length: 26 | ||
< apigw-requestid: D3bfpidOoAMESiQ= | ||
< | ||
* Connection #0 to host 6sm6270j21.execute-api.us-east-1.amazonaws.com left intact | ||
{"message":"Unauthorized"} | ||
``` | ||
|
||
When invoking the Lambda function with the `Authorization` header, the response is a `200 OK` status code. Note that the Lambda Authorizer function is configured to accept any value in the `Authorization` header. | ||
|
||
```bash | ||
curl -v -H 'Authorization: 123' https://a5q74es3k2.execute-api.us-east-1.amazonaws.com/demo | ||
... | ||
> GET /demo HTTP/2 | ||
> Host: 6sm6270j21.execute-api.us-east-1.amazonaws.com | ||
> User-Agent: curl/8.7.1 | ||
> Accept: */* | ||
> Authorization: 123 | ||
> | ||
* Request completely sent off | ||
< HTTP/2 200 | ||
< date: Sat, 04 Jan 2025 14:04:43 GMT | ||
< content-type: application/json | ||
< content-length: 911 | ||
< apigw-requestid: D3bvRjJcoAMEaig= | ||
< | ||
* Connection #0 to host 6sm6270j21.execute-api.us-east-1.amazonaws.com left intact | ||
{"headers":{"x-forwarded-port":"443","x-forwarded-proto":"https","host":"6sm6270j21.execute-api.us-east-1.amazonaws.com","user-agent":"curl\/8.7.1","accept":"*\/*","content-length":"0","x-amzn-trace-id":"Root=1-67793ffa-05f1296f1a52f8a066180020","authorization":"123","x-forwarded-for":"81.49.207.77"},"routeKey":"ANY \/demo","version":"2.0","rawQueryString":"","isBase64Encoded":false,"queryStringParameters":{},"pathParameters":{},"rawPath":"\/demo","cookies":[],"requestContext":{"domainPrefix":"6sm6270j21","requestId":"D3bvRjJcoAMEaig=","domainName":"6sm6270j21.execute-api.us-east-1.amazonaws.com","stage":"$default","authorizer":{"lambda":{"abc1":"xyz1"}},"timeEpoch":1735999482988,"accountId":"401955065246","time":"04\/Jan\/2025:14:04:42 +0000","http":{"method":"GET","sourceIp":"81.49.207.77","path":"\/demo","userAgent":"curl\/8.7.1","protocol":"HTTP\/1.1"},"apiId":"6sm6270j21"},"stageVariables":{}} | ||
``` | ||
|
||
## Undeploy | ||
|
||
When done testing, you can delete the infrastructure with this command. | ||
|
||
```bash | ||
sam delete --stack-name APIGatewayWithLambdaAuthorizer | ||
``` |
30 changes: 30 additions & 0 deletions
30
Examples/APIGateway+LambdaAuthorizer/Sources/APIGatewayLambda/main.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the SwiftAWSLambdaRuntime open source project | ||
// | ||
// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors | ||
// Licensed under Apache License v2.0 | ||
// | ||
// See LICENSE.txt for license information | ||
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
import AWSLambdaEvents | ||
import AWSLambdaRuntime | ||
|
||
let runtime = LambdaRuntime { | ||
(event: APIGatewayV2Request, context: LambdaContext) -> APIGatewayV2Response in | ||
|
||
var header = HTTPHeaders() | ||
context.logger.debug("HTTP API Message received") | ||
|
||
header["content-type"] = "application/json" | ||
|
||
// echo the request in the response | ||
return try APIGatewayV2Response(statusCode: .ok, headers: header, encodableBody: event) | ||
} | ||
|
||
try await runtime.run() |
79 changes: 79 additions & 0 deletions
79
Examples/APIGateway+LambdaAuthorizer/Sources/AuthorizerLambda/main.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the SwiftAWSLambdaRuntime open source project | ||
// | ||
// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors | ||
// Licensed under Apache License v2.0 | ||
// | ||
// See LICENSE.txt for license information | ||
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
import AWSLambdaEvents | ||
import AWSLambdaRuntime | ||
|
||
// | ||
// This is an example of a policy authorizer that always authorizes the request. | ||
// The policy authorizer returns an IAM policy document that defines what the Lambda function caller can do and optional context key-value pairs | ||
// | ||
// This code is shown for the example only and is not used in this demo. | ||
// This code doesn't perform any type of token validation. It should be used as a reference only. | ||
let policyAuthorizerHandler: | ||
(APIGatewayLambdaAuthorizerRequest, LambdaContext) async throws -> APIGatewayLambdaAuthorizerPolicyResponse = { | ||
(request: APIGatewayLambdaAuthorizerRequest, context: LambdaContext) in | ||
|
||
context.logger.debug("+++ Policy Authorizer called +++") | ||
|
||
// typically, this function will check the validity of the incoming token received in the request | ||
|
||
// then it creates and returns a response | ||
return APIGatewayLambdaAuthorizerPolicyResponse( | ||
principalId: "John Appleseed", | ||
|
||
// this policy allows the caller to invoke any API Gateway endpoint | ||
policyDocument: .init(statement: [ | ||
.init( | ||
action: "execute-api:Invoke", | ||
effect: .allow, | ||
resource: "*" | ||
) | ||
|
||
]), | ||
|
||
// this is additional context we want to return to the caller | ||
context: [ | ||
"abc1": "xyz1", | ||
"abc2": "xyz2", | ||
] | ||
) | ||
} | ||
|
||
// | ||
// This is an example of a simple authorizer that always authorizes the request. | ||
// A simple authorizer returns a yes/no decision and optional context key-value pairs | ||
// | ||
// This code doesn't perform any type of token validation. It should be used as a reference only. | ||
let simpleAuthorizerHandler: | ||
(APIGatewayLambdaAuthorizerRequest, LambdaContext) async throws -> APIGatewayLambdaAuthorizerSimpleResponse = { | ||
(_: APIGatewayLambdaAuthorizerRequest, context: LambdaContext) in | ||
|
||
context.logger.debug("+++ Simple Authorizer called +++") | ||
|
||
// typically, this function will check the validity of the incoming token received in the request | ||
|
||
return APIGatewayLambdaAuthorizerSimpleResponse( | ||
// this is the authorization decision: yes or no | ||
isAuthorized: true, | ||
|
||
// this is additional context we want to return to the caller | ||
context: ["abc1": "xyz1"] | ||
) | ||
} | ||
|
||
// create the runtime and start polling for new events. | ||
// in this demo we use the simple authorizer handler | ||
let runtime = LambdaRuntime(body: simpleAuthorizerHandler) | ||
try await runtime.run() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
AWSTemplateFormatVersion: '2010-09-09' | ||
Transform: AWS::Serverless-2016-10-31 | ||
Description: SAM Template for APIGateway Lambda Example | ||
|
||
Resources: | ||
# The API Gateway | ||
MyProtectedApi: | ||
Type: AWS::Serverless::HttpApi | ||
Properties: | ||
Auth: | ||
DefaultAuthorizer: MyLambdaRequestAuthorizer | ||
Authorizers: | ||
MyLambdaRequestAuthorizer: | ||
FunctionArn: !GetAtt AuthorizerLambda.Arn | ||
Identity: | ||
Headers: | ||
- Authorization | ||
AuthorizerPayloadFormatVersion: "2.0" | ||
EnableSimpleResponses: true | ||
|
||
# Give the API Gateway permissions to invoke the Lambda authorizer | ||
AuthorizerPermission: | ||
Type: AWS::Lambda::Permission | ||
Properties: | ||
Action: lambda:InvokeFunction | ||
FunctionName: !Ref AuthorizerLambda | ||
Principal: apigateway.amazonaws.com | ||
SourceArn: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${MyProtectedApi}/* | ||
|
||
# Lambda business function | ||
APIGatewayLambda: | ||
Type: AWS::Serverless::Function | ||
Properties: | ||
CodeUri: .build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/APIGatewayLambda/APIGatewayLambda.zip | ||
Timeout: 60 | ||
Handler: swift.bootstrap # ignored by the Swift runtime | ||
Runtime: provided.al2 | ||
MemorySize: 512 | ||
Architectures: | ||
- arm64 | ||
Environment: | ||
Variables: | ||
# by default, AWS Lambda runtime produces no log | ||
# use `LOG_LEVEL: debug` for for lifecycle and event handling information | ||
# use `LOG_LEVEL: trace` for detailed input event information | ||
LOG_LEVEL: debug | ||
Events: | ||
HttpApiEvent: | ||
Type: HttpApi | ||
Properties: | ||
ApiId: !Ref MyProtectedApi | ||
Path: /demo | ||
Method: ANY | ||
|
||
# Lambda authorizer function | ||
AuthorizerLambda: | ||
Type: AWS::Serverless::Function | ||
Properties: | ||
CodeUri: .build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/AuthorizerLambda/AuthorizerLambda.zip | ||
Timeout: 29 # max 29 seconds for Lambda authorizers | ||
Handler: swift.bootstrap # ignored by the Swift runtime | ||
Runtime: provided.al2 | ||
MemorySize: 512 | ||
Architectures: | ||
- arm64 | ||
Environment: | ||
Variables: | ||
# by default, AWS Lambda runtime produces no log | ||
# use `LOG_LEVEL: debug` for for lifecycle and event handling information | ||
# use `LOG_LEVEL: trace` for detailed input event information | ||
LOG_LEVEL: debug | ||
|
||
Outputs: | ||
# print API Gateway endpoint | ||
APIGatewayEndpoint: | ||
Description: API Gateway endpoint URI | ||
Value: !Sub "https://${MyProtectedApi}.execute-api.${AWS::Region}.amazonaws.com/demo" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As above, one needs to change