Skip to content

Commit 051029f

Browse files
authored
[examples] Add an example of an APIGateway Lambda Authorizer (#453)
Add an example of an APIGateway Lambda Authorizer ### Motivation: Lambda Authorizers allow developers to write custom logic to validate HTTP REST requests. This example shows how to write a Lambda Authorizer in Swift and how to configure an API Gateway to use a Lambda Authorizer. This example use a SAM template to deploy the resources. ### Modifications: - Add `Examples/APIGateway+LambdaAuthorizer` directory with the source code, a `template.yaml`, and a `README` file. - Add a reference to the new example in `Examples/README` - Modify CI to build the new example ### Result: There is a new example showing how to develop a Lambda authorizer in Swift and how to attach it to an APIGateway with SAM
1 parent 8f7788f commit 051029f

File tree

8 files changed

+372
-1
lines changed

8 files changed

+372
-1
lines changed

.github/workflows/pull_request.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ jobs:
3636
# We pass the list of examples here, but we can't pass an array as argument
3737
# Instead, we pass a String with a valid JSON array.
3838
# The workaround is mentioned here https://github.com/orgs/community/discussions/11692
39-
examples: "[ 'APIGateway', 'BackgroundTasks', 'HelloJSON', 'HelloWorld', 'S3_AWSSDK', 'S3_Soto', 'Streaming', 'Testing', 'Tutorial' ]"
39+
examples: "[ 'APIGateway', 'APIGateway+LambdaAuthorizer', 'BackgroundTasks', 'HelloJSON', 'HelloWorld', 'S3_AWSSDK', 'S3_Soto', 'Streaming', 'Testing', 'Tutorial' ]"
4040

4141
archive_plugin_enabled: true
4242

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
samconfig.toml
2+
Makefile
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// swift-tools-version:6.0
2+
3+
import PackageDescription
4+
5+
// needed for CI to test the local version of the library
6+
import struct Foundation.URL
7+
8+
#if os(macOS)
9+
let platforms: [PackageDescription.SupportedPlatform]? = [.macOS(.v15)]
10+
#else
11+
let platforms: [PackageDescription.SupportedPlatform]? = nil
12+
#endif
13+
14+
let package = Package(
15+
name: "swift-aws-lambda-runtime-example",
16+
platforms: [.macOS(.v15)],
17+
products: [
18+
.executable(name: "APIGatewayLambda", targets: ["APIGatewayLambda"]),
19+
.executable(name: "AuthorizerLambda", targets: ["AuthorizerLambda"]),
20+
],
21+
dependencies: [
22+
// during CI, the dependency on local version of swift-aws-lambda-runtime is added dynamically below
23+
.package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", branch: "main"),
24+
.package(url: "https://github.com/swift-server/swift-aws-lambda-events.git", branch: "main"),
25+
],
26+
targets: [
27+
.executableTarget(
28+
name: "APIGatewayLambda",
29+
dependencies: [
30+
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"),
31+
.product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"),
32+
]
33+
),
34+
.executableTarget(
35+
name: "AuthorizerLambda",
36+
dependencies: [
37+
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"),
38+
.product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"),
39+
]
40+
),
41+
]
42+
)
43+
44+
if let localDepsPath = Context.environment["LAMBDA_USE_LOCAL_DEPS"],
45+
localDepsPath != "",
46+
let v = try? URL(fileURLWithPath: localDepsPath).resourceValues(forKeys: [.isDirectoryKey]),
47+
v.isDirectory == true
48+
{
49+
// when we use the local runtime as deps, let's remove the dependency added above
50+
let indexToRemove = package.dependencies.firstIndex { dependency in
51+
if case .sourceControl(
52+
name: _,
53+
location: "https://github.com/swift-server/swift-aws-lambda-runtime.git",
54+
requirement: _
55+
) = dependency.kind {
56+
return true
57+
}
58+
return false
59+
}
60+
if let indexToRemove {
61+
package.dependencies.remove(at: indexToRemove)
62+
}
63+
64+
// then we add the dependency on LAMBDA_USE_LOCAL_DEPS' path (typically ../..)
65+
print("[INFO] Compiling against swift-aws-lambda-runtime located at \(localDepsPath)")
66+
package.dependencies += [
67+
.package(name: "swift-aws-lambda-runtime", path: localDepsPath)
68+
]
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Lambda Authorizer with API Gateway
2+
3+
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.
4+
5+
>[!NOTE]
6+
> 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.
7+
8+
## Code
9+
10+
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.
11+
12+
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.
13+
14+
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.
15+
16+
## Build & Package
17+
18+
To build the package, type the following commands.
19+
20+
```bash
21+
swift build
22+
swift package archive --allow-network-connections docker
23+
```
24+
25+
If there is no error, there are two ZIP files ready to deploy, one for the authorizer function and one for the business function.
26+
The ZIP file are located under `.build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager`
27+
28+
## Deploy
29+
30+
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.
31+
32+
**Prerequisites** : Install the [SAM CLI](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html)
33+
34+
The example directory contains a file named `template.yaml` that describes the deployment.
35+
36+
To actually deploy your Lambda function and create the infrastructure, type the following `sam` command.
37+
38+
```bash
39+
sam deploy \
40+
--resolve-s3 \
41+
--template-file template.yaml \
42+
--stack-name APIGatewayWithLambdaAuthorizer \
43+
--capabilities CAPABILITY_IAM
44+
```
45+
46+
At the end of the deployment, the script lists the API Gateway endpoint.
47+
The output is similar to this one.
48+
49+
```
50+
-----------------------------------------------------------------------------------------------------------------------------
51+
Outputs
52+
-----------------------------------------------------------------------------------------------------------------------------
53+
Key APIGatewayEndpoint
54+
Description API Gateway endpoint URI
55+
Value https://a5q74es3k2.execute-api.us-east-1.amazonaws.com/demo
56+
-----------------------------------------------------------------------------------------------------------------------------
57+
```
58+
59+
## Invoke your Lambda function
60+
61+
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.
62+
63+
When invoking the Lambda function without `Authorization` header, the response is a `401 Unauthorized` error.
64+
65+
```bash
66+
curl -v https://a5q74es3k2.execute-api.us-east-1.amazonaws.com/demo
67+
...
68+
> GET /demo HTTP/2
69+
> Host: 6sm6270j21.execute-api.us-east-1.amazonaws.com
70+
> User-Agent: curl/8.7.1
71+
> Accept: */*
72+
>
73+
* Request completely sent off
74+
< HTTP/2 401
75+
< date: Sat, 04 Jan 2025 14:03:02 GMT
76+
< content-type: application/json
77+
< content-length: 26
78+
< apigw-requestid: D3bfpidOoAMESiQ=
79+
<
80+
* Connection #0 to host 6sm6270j21.execute-api.us-east-1.amazonaws.com left intact
81+
{"message":"Unauthorized"}
82+
```
83+
84+
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.
85+
86+
```bash
87+
curl -v -H 'Authorization: 123' https://a5q74es3k2.execute-api.us-east-1.amazonaws.com/demo
88+
...
89+
> GET /demo HTTP/2
90+
> Host: 6sm6270j21.execute-api.us-east-1.amazonaws.com
91+
> User-Agent: curl/8.7.1
92+
> Accept: */*
93+
> Authorization: 123
94+
>
95+
* Request completely sent off
96+
< HTTP/2 200
97+
< date: Sat, 04 Jan 2025 14:04:43 GMT
98+
< content-type: application/json
99+
< content-length: 911
100+
< apigw-requestid: D3bvRjJcoAMEaig=
101+
<
102+
* Connection #0 to host 6sm6270j21.execute-api.us-east-1.amazonaws.com left intact
103+
{"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":{}}
104+
```
105+
106+
## Undeploy
107+
108+
When done testing, you can delete the infrastructure with this command.
109+
110+
```bash
111+
sam delete --stack-name APIGatewayWithLambdaAuthorizer
112+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftAWSLambdaRuntime open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import AWSLambdaEvents
16+
import AWSLambdaRuntime
17+
18+
let runtime = LambdaRuntime {
19+
(event: APIGatewayV2Request, context: LambdaContext) -> APIGatewayV2Response in
20+
21+
var header = HTTPHeaders()
22+
context.logger.debug("HTTP API Message received")
23+
24+
header["content-type"] = "application/json"
25+
26+
// echo the request in the response
27+
return try APIGatewayV2Response(statusCode: .ok, headers: header, encodableBody: event)
28+
}
29+
30+
try await runtime.run()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftAWSLambdaRuntime open source project
4+
//
5+
// Copyright (c) 2024 Apple Inc. and the SwiftAWSLambdaRuntime project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import AWSLambdaEvents
16+
import AWSLambdaRuntime
17+
18+
//
19+
// This is an example of a policy authorizer that always authorizes the request.
20+
// The policy authorizer returns an IAM policy document that defines what the Lambda function caller can do and optional context key-value pairs
21+
//
22+
// This code is shown for the example only and is not used in this demo.
23+
// This code doesn't perform any type of token validation. It should be used as a reference only.
24+
let policyAuthorizerHandler:
25+
(APIGatewayLambdaAuthorizerRequest, LambdaContext) async throws -> APIGatewayLambdaAuthorizerPolicyResponse = {
26+
(request: APIGatewayLambdaAuthorizerRequest, context: LambdaContext) in
27+
28+
context.logger.debug("+++ Policy Authorizer called +++")
29+
30+
// typically, this function will check the validity of the incoming token received in the request
31+
32+
// then it creates and returns a response
33+
return APIGatewayLambdaAuthorizerPolicyResponse(
34+
principalId: "John Appleseed",
35+
36+
// this policy allows the caller to invoke any API Gateway endpoint
37+
policyDocument: .init(statement: [
38+
.init(
39+
action: "execute-api:Invoke",
40+
effect: .allow,
41+
resource: "*"
42+
)
43+
44+
]),
45+
46+
// this is additional context we want to return to the caller
47+
context: [
48+
"abc1": "xyz1",
49+
"abc2": "xyz2",
50+
]
51+
)
52+
}
53+
54+
//
55+
// This is an example of a simple authorizer that always authorizes the request.
56+
// A simple authorizer returns a yes/no decision and optional context key-value pairs
57+
//
58+
// This code doesn't perform any type of token validation. It should be used as a reference only.
59+
let simpleAuthorizerHandler:
60+
(APIGatewayLambdaAuthorizerRequest, LambdaContext) async throws -> APIGatewayLambdaAuthorizerSimpleResponse = {
61+
(_: APIGatewayLambdaAuthorizerRequest, context: LambdaContext) in
62+
63+
context.logger.debug("+++ Simple Authorizer called +++")
64+
65+
// typically, this function will check the validity of the incoming token received in the request
66+
67+
return APIGatewayLambdaAuthorizerSimpleResponse(
68+
// this is the authorization decision: yes or no
69+
isAuthorized: true,
70+
71+
// this is additional context we want to return to the caller
72+
context: ["abc1": "xyz1"]
73+
)
74+
}
75+
76+
// create the runtime and start polling for new events.
77+
// in this demo we use the simple authorizer handler
78+
let runtime = LambdaRuntime(body: simpleAuthorizerHandler)
79+
try await runtime.run()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
AWSTemplateFormatVersion: '2010-09-09'
2+
Transform: AWS::Serverless-2016-10-31
3+
Description: SAM Template for APIGateway Lambda Example
4+
5+
Resources:
6+
# The API Gateway
7+
MyProtectedApi:
8+
Type: AWS::Serverless::HttpApi
9+
Properties:
10+
Auth:
11+
DefaultAuthorizer: MyLambdaRequestAuthorizer
12+
Authorizers:
13+
MyLambdaRequestAuthorizer:
14+
FunctionArn: !GetAtt AuthorizerLambda.Arn
15+
Identity:
16+
Headers:
17+
- Authorization
18+
AuthorizerPayloadFormatVersion: "2.0"
19+
EnableSimpleResponses: true
20+
21+
# Give the API Gateway permissions to invoke the Lambda authorizer
22+
AuthorizerPermission:
23+
Type: AWS::Lambda::Permission
24+
Properties:
25+
Action: lambda:InvokeFunction
26+
FunctionName: !Ref AuthorizerLambda
27+
Principal: apigateway.amazonaws.com
28+
SourceArn: !Sub arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${MyProtectedApi}/*
29+
30+
# Lambda business function
31+
APIGatewayLambda:
32+
Type: AWS::Serverless::Function
33+
Properties:
34+
CodeUri: .build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/APIGatewayLambda/APIGatewayLambda.zip
35+
Timeout: 60
36+
Handler: swift.bootstrap # ignored by the Swift runtime
37+
Runtime: provided.al2
38+
MemorySize: 512
39+
Architectures:
40+
- arm64
41+
Environment:
42+
Variables:
43+
# by default, AWS Lambda runtime produces no log
44+
# use `LOG_LEVEL: debug` for for lifecycle and event handling information
45+
# use `LOG_LEVEL: trace` for detailed input event information
46+
LOG_LEVEL: debug
47+
Events:
48+
HttpApiEvent:
49+
Type: HttpApi
50+
Properties:
51+
ApiId: !Ref MyProtectedApi
52+
Path: /demo
53+
Method: ANY
54+
55+
# Lambda authorizer function
56+
AuthorizerLambda:
57+
Type: AWS::Serverless::Function
58+
Properties:
59+
CodeUri: .build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/AuthorizerLambda/AuthorizerLambda.zip
60+
Timeout: 29 # max 29 seconds for Lambda authorizers
61+
Handler: swift.bootstrap # ignored by the Swift runtime
62+
Runtime: provided.al2
63+
MemorySize: 512
64+
Architectures:
65+
- arm64
66+
Environment:
67+
Variables:
68+
# by default, AWS Lambda runtime produces no log
69+
# use `LOG_LEVEL: debug` for for lifecycle and event handling information
70+
# use `LOG_LEVEL: trace` for detailed input event information
71+
LOG_LEVEL: debug
72+
73+
Outputs:
74+
# print API Gateway endpoint
75+
APIGatewayEndpoint:
76+
Description: API Gateway endpoint URI
77+
Value: !Sub "https://${MyProtectedApi}.execute-api.${AWS::Region}.amazonaws.com/demo"

Examples/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ This directory contains example code for Lambda functions.
1818

1919
- **[API Gateway](APIGateway/README.md)**: an HTTPS REST API with [Amazon API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html) and a Lambda function as backend (requires [AWS SAM](https://aws.amazon.com/serverless/sam/)).
2020

21+
- **[API Gateway with Lambda Authorizer](APIGateway+LambdaAuthorizer/README.md)**: an HTTPS REST API with [Amazon API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html) protected by a Lambda authorizer (requires [AWS SAM](https://aws.amazon.com/serverless/sam/)).
22+
2123
- **[BackgroundTasks](BackgroundTasks/README.md)**: a Lambda function that continues to run background tasks after having sent the response (requires [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)).
2224

2325
- **[CDK](CDK/README.md)**: a simple example of an AWS Lambda function invoked through an Amazon API Gateway and deployed with the Cloud Development Kit (CDK).

0 commit comments

Comments
 (0)