Skip to content

feat: module to ease cors configuration #831

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
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
199 changes: 199 additions & 0 deletions docs/utilities/cors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
---
title: Cors
description: Utility
---

The Cors utility helps configuring [CORS (Cross-Origin Resource Sharing)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) for your Lambda function when used with API Gateway and Lambda proxy integration.
When configured as Lambda proxy integration, the function must set CORS HTTP headers in the response ([doc](https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors.html#:~:text=Enabling%20CORS%20support%20for%20Lambda%20or%20HTTP%20proxy%20integrations)).

**Key features**

* Automatically set the CORS HTTP headers in the response.
* Multi-origins is supported, only the origin matching the request origin will be returned.
* Support environment variables or programmatic configuration

## Install

To install this utility, add the following dependency to your project.

=== "Maven"

```xml
<dependency>
<groupId>software.amazon.lambda</groupId>
<artifactId>powertools-cors</artifactId>
<version>{{ powertools.version }}</version>
</dependency>

<!-- configure the aspectj-maven-plugin to compile-time weave (CTW) the aws-lambda-powertools-java aspects into your project -->
<build>
<plugins>
...
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.14.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<complianceLevel>1.8</complianceLevel>
<aspectLibraries>
<aspectLibrary>
<groupId>software.amazon.lambda</groupId>
<artifactId>powertools-cors</artifactId>
</aspectLibrary>
...
</aspectLibraries>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
...
</plugins>
</build>
```
=== "Gradle"

```groovy
plugins{
id 'java'
id 'io.freefair.aspectj.post-compile-weaving' version '6.3.0'
}

dependencies {
...
aspect 'software.amazon.lambda:powertools-cors:{{ powertools.version }}'
}
```

## Cors Annotation

You can use the `@CrossOrigin` annotation on the `handleRequest` method of a class that implements `RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent>`

=== "Cross Origin Annotation"

```java hl_lines="3"
public class FunctionProxy implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

@CrossOrigin
public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) {
// ...
return new APIGatewayProxyResponseEvent()
.withStatusCode(200)
.withHeaders(headers)
.withBody(body);
}
}
```

## Configuration

### Using the annotation

=== "FunctionProxy.java"

The annotation provides all the parameters required to set up the different CORS headers:

| parameter | Default | Description |
|----------------------|-----------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------|
| **allowedHeaders** | `Authorization, *` ([*](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers#directives)) | [`Access-Control-Allow-Headers`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers) header |
| **exposedHeaders** | `*` | [`Access-Control-Expose-Headers`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers) header |
| **origins** | `*` | [`Access-Control-Allow-Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin) header |
| **methods** | `*` | [`Access-Control-Allow-Methods`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods) header |
| **allowCredentials** | `true` | [`Access-Control-Allow-Credentials`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials) header |
| **maxAge** | `29` | [`Access-Control-Max-Age`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age) header |

**Example:**

=== "FunctionProxy.java"

```java hl_lines="3-7"
public class FunctionProxy implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

@CrossOrigin(
origins = "http://origin.com, https://other.origin.com",
allowedHeaders = "Authorization, Content-Type, X-API-Key",
methods = "POST, OPTIONS"
)
public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) {
// ...
return new APIGatewayProxyResponseEvent()
.withStatusCode(200)
.withHeaders(headers)
.withBody(body);
}
}
```


### Using Environment variables

You can configure the CORS header values in the environment variables of your Lambda function. They will have precedence over the annotation configuration.
This way you can externalize the configuration and change it without changing the code.

| Environment variable | Default | Description |
|--------------------------------------|-----------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------|
| **ACCESS_CONTROL_ALLOW_HEADERS** | `Authorization, *` ([*](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers#directives)) | [`Access-Control-Allow-Headers`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers) header |
| **ACCESS_CONTROL_EXPOSE_HEADERS** | `*` | [`Access-Control-Expose-Headers`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers) header |
| **ACCESS_CONTROL_ALLOW_ORIGIN** | `*` | [`Access-Control-Allow-Origin`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin) header |
| **ACCESS_CONTROL_ALLOW_METHODS** | `*` | [`Access-Control-Allow-Methods`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods) header |
| **ACCESS_CONTROL_ALLOW_CREDENTIALS** | `true` | [`Access-Control-Allow-Credentials`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials) header |
| **ACCESS_CONTROL_MAX_AGE** | `29` | [`Access-Control-Max-Age`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age) header |

**Example:**

=== "SAM template"

```yaml hl_lines="12-17"
CorsFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: Function
Handler: CorsFunction::handleRequest
Runtime: java11
Architectures:
- x86_64
MemorySize: 512
Environment:
Variables:
ACCESS_CONTROL_ALLOW_HEADERS: 'Authorization, Content-Type, X-API-Key'
ACCESS_CONTROL_EXPOSE_HEADERS: '*'
ACCESS_CONTROL_ALLOW_ORIGIN: 'https://mydomain.com'
ACCESS_CONTROL_ALLOW_METHODS: 'OPTIONS, POST'
ACCESS_CONTROL_MAX_AGE: '300'
ACCESS_CONTROL_ALLOW_CREDENTIALS: 'true'
Events:
MyApi:
Type: Api
Properties:
Path: /cors
Method: post
```

## Advanced information about origin configuration
Browsers do no support multiple origins (comma-separated) in the `Access-Control-Allow-Origin` header. Only one must be provided.
If your backend can be reached by multiple frontend applications (with different origins),
the function must return the `Access-Control-Allow-Origin` header that matches the `origin` header in the request.

Lambda Powertools handles this for you. You can configure multiple origins, separated by commas:

=== "Multiple origins"

```java hl_lines="2"
@CrossOrigin(
origins = "http://origin.com, https://other.origin.com"
)
```

!!! warning "Origins must be well-formed"
Origins must be well-formed URLs: `{protocol}://{host}[:{port}]` where protocol can be `http` or `https` and port is optional. [Mode details](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#definition_of_an_origin).

`*`, `http://*` and `https://*` are also valid origins.

!!! info "`Vary` header"
Note that when returning a specific value (rather than `*`), the `Vary` header must be set to `Origin` ([doc](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin#cors_and_caching)). Lambda Powertools handles this for you.
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ nav:
- utilities/validation.md
- utilities/custom_resources.md
- utilities/serialization.md
- utilities/cors.md

theme:
name: material
Expand Down
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
<module>powertools-test-suite</module>
<module>powertools-cloudformation</module>
<module>powertools-idempotency</module>
<module>powertools-cors</module>
</modules>

<scm>
Expand Down
117 changes: 117 additions & 0 deletions powertools-cors/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>powertools-parent</artifactId>
<groupId>software.amazon.lambda</groupId>
<version>1.12.0</version>
</parent>
<artifactId>powertools-cors</artifactId>
<name>AWS Lambda Powertools for Java library CORS Configuration</name>

<url>https://aws.amazon.com/lambda/</url>
<issueManagement>
<system>GitHub Issues</system>
<url>https://github.com/awslabs/aws-lambda-powertools-java/issues</url>
</issueManagement>
<scm>
<url>https://github.com/awslabs/aws-lambda-powertools-java.git</url>
</scm>
<developers>
<developer>
<name>AWS Lambda Powertools team</name>
<organization>Amazon Web Services</organization>
<organizationUrl>https://aws.amazon.com/</organizationUrl>
</developer>
</developers>

<distributionManagement>
<snapshotRepository>
<id>ossrh</id>
<url>https://aws.oss.sonatype.org/content/repositories/snapshots</url>
</snapshotRepository>
</distributionManagement>

<dependencies>
<dependency>
<groupId>software.amazon.lambda</groupId>
<artifactId>powertools-core</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-events</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
</dependency>

<!-- Test dependencies -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-lambda-java-tests</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit-pioneer</groupId>
<artifactId>junit-pioneer</artifactId>
<version>1.6.2</version>
<scope>test</scope>
</dependency>
</dependencies>

<profiles>
<profile>
<id>jdk16</id>
<activation>
<jdk>[16,)</jdk>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
<configuration>
<argLine>
--add-opens java.base/java.util=ALL-UNNAMED
--add-opens java.base/java.lang=ALL-UNNAMED
</argLine>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2022 Amazon.com, Inc. or its affiliates.
* Licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package software.amazon.lambda.powertools.cors;

public interface Constants {
String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers";
String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers";
String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods";
String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials";
String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age";

String VARY = "Vary";
String VARY_ORIGIN = "Origin";

String ENV_ACCESS_CONTROL_ALLOW_HEADERS = "ACCESS_CONTROL_ALLOW_HEADERS";
String ENV_ACCESS_CONTROL_EXPOSE_HEADERS = "ACCESS_CONTROL_EXPOSE_HEADERS";
String ENV_ACCESS_CONTROL_ALLOW_ORIGIN = "ACCESS_CONTROL_ALLOW_ORIGIN";
String ENV_ACCESS_CONTROL_ALLOW_METHODS = "ACCESS_CONTROL_ALLOW_METHODS";
String ENV_ACCESS_CONTROL_ALLOW_CREDENTIALS = "ACCESS_CONTROL_ALLOW_CREDENTIALS";
String ENV_ACCESS_CONTROL_MAX_AGE = "ACCESS_CONTROL_MAX_AGE";

String WILDCARD = "*";

String DEFAULT_ACCESS_CONTROL_ALLOW_HEADERS = "Authorization, *";
String DEFAULT_ACCESS_CONTROL_EXPOSE_HEADERS = WILDCARD;
String DEFAULT_ACCESS_CONTROL_ALLOW_METHODS = WILDCARD;
String DEFAULT_ACCESS_CONTROL_ALLOW_ORIGIN = WILDCARD;
boolean DEFAULT_ACCESS_CONTROL_ALLOW_CREDENTIALS = true;
int DEFAULT_ACCESS_CONTROL_MAX_AGE = 29;

}
Loading