Skip to content

add Lambda Authorizer example #3

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

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
# We pass the list of examples here, but we can't pass an array as argument
# Instead, we pass a String with a valid JSON array.
# The workaround is mentioned here https://github.com/orgs/community/discussions/11692
examples: "[ 'APIGateway', 'BackgroundTasks', 'HelloJSON', 'HelloWorld', 'S3_AWSSDK', 'S3_Soto', 'Streaming', 'Testing', 'Tutorial' ]"
examples: "[ 'APIGateway', 'APIGateway+LambdaAuthorizer', 'BackgroundTasks', 'HelloJSON', 'HelloWorld', 'S3_AWSSDK', 'S3_Soto', 'Streaming', 'Testing', 'Tutorial' ]"

archive_plugin_enabled: true

Expand Down
2 changes: 2 additions & 0 deletions Examples/APIGateway+LambdaAuthorizer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
samconfig.toml
Makefile
69 changes: 69 additions & 0 deletions Examples/APIGateway+LambdaAuthorizer/Package.swift
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)
]
}
110 changes: 110 additions & 0 deletions Examples/APIGateway+LambdaAuthorizer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# API Gateway

This is an example of a Lambda Authorizer function. There are two Lambda function in this example. The first one is the authorizer function. The second one is the business function. The business function is exposed throigh a REST API using the API Gateway. The API Gateway is configured to use the authorizer function to authorize the requests.

## 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 function 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 URLI
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://6sm6270j21.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.

```bash
curl -v -H 'Authorization: 123' https://6sm6270j21.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
```
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()
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()
77 changes: 77 additions & 0 deletions Examples/APIGateway+LambdaAuthorizer/template.yaml
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"
2 changes: 2 additions & 0 deletions Examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ This directory contains example code for Lambda functions.

- **[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/)).

- **[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/)).

- **[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)).

- **[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).
Expand Down
Loading