Skip to content

Bug: CORS middleware doesn't handle preflight OPTIONS requests automatically #4506

@dreamorosi

Description

@dreamorosi

Expected Behavior

Based on the original discussion in issue #4446, simply enabling the CORS middleware should be sufficient to support a complete browser request cycle, including handling preflight OPTIONS requests automatically without requiring explicit route definitions.

Current Behavior

When using the CORS middleware with the experimental REST event handler, preflight OPTIONS requests result in a 404 error because the middleware doesn't appear to short-circuit the normal routing process. This requires manually defining OPTIONS routes for each endpoint that needs CORS support, which defeats the purpose of having automatic CORS handling.

Browser error: Failed to load resource: Preflight response is not successful. Status code: 404

Code snippet

import { Router } from '@aws-lambda-powertools/event-handler/experimental-rest';
import {
  compress,
  cors,
} from '@aws-lambda-powertools/event-handler/experimental-rest/middleware';
import { Logger } from '@aws-lambda-powertools/logger';
import type { Context } from 'aws-lambda';

const logger = new Logger({ serviceName: 'pong-service' });
const app = new Router();

app.use(async (_, reqCtx, next) => {
  logger.info('Request received', {
    headers: reqCtx.event.headers,
  });
  await next();
});
app.use(compress());
app.use(cors());

app.get('/ping', async () => {
  return { message: 'pong' };
});

app.post('/submit', async (_, reqCtx) => {
  const body = await reqCtx.request.json();
  logger.info('Form submitted', { body });
  return { message: 'submitted' };
});

export const handler = async (event: unknown, context: Context) =>
  app.resolve(event, context);

Steps to Reproduce

  1. Set up a Lambda function using the experimental REST event handler with CORS middleware as shown above
  2. Create an HTML form that submits a POST request to the /submit endpoint from a different origin (see below for HTML code)
  3. Submit the form from a browser
  4. Observe that the preflight OPTIONS request fails with a 404 error
Click here for webpage w/ form code ```html <title>Comment Form</title> <style> body { font-family: Arial, sans-serif; max-width: 500px; margin: 50px auto; padding: 20px; }
    .form-group {
        margin-bottom: 15px;
    }

    label {
        display: block;
        margin-bottom: 5px;
        font-weight: bold;
    }

    input[type="text"] {
        width: 100%;
        padding: 10px;
        border: 1px solid #ddd;
        border-radius: 4px;
        box-sizing: border-box;
    }

    button {
        background-color: #007bff;
        color: white;
        padding: 10px 20px;
        border: none;
        border-radius: 4px;
        cursor: pointer;
    }

    button:hover {
        background-color: #0056b3;
    }

    button:disabled {
        background-color: #ccc;
        cursor: not-allowed;
    }

    .message {
        margin-top: 15px;
        padding: 10px;
        border-radius: 4px;
    }

    .success {
        background-color: #d4edda;
        color: #155724;
        border: 1px solid #c3e6cb;
    }

    .error {
        background-color: #f8d7da;
        color: #721c24;
        border: 1px solid #f5c6cb;
    }
</style>

Submit Comment

<form id="commentForm">
    <div class="form-group">
        <label for="comment">Comment:</label>
        <input type="text" id="comment" name="comment" required>
    </div>

    <button type="submit" id="submitBtn">Submit Comment</button>
</form>

<div id="message"></div>

<script>
    document.getElementById('commentForm').addEventListener('submit', async function(e) {
        e.preventDefault();

        const submitBtn = document.getElementById('submitBtn');
        const messageDiv = document.getElementById('message');
        const commentInput = document.getElementById('comment');
        const comment = commentInput.value.trim();

        if (!comment) {
            showMessage('Please enter a comment', 'error');
            return;
        }

        submitBtn.disabled = true;
        submitBtn.textContent = 'Submitting...';
        messageDiv.innerHTML = '';

        try {
            const response = await fetch('your API/prod/submit', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({ comment: comment })
            });

            if (response.ok) {
                const result = await response.json();
                showMessage('Comment submitted successfully!', 'success');
                commentInput.value = '';
            } else {
                showMessage('Failed to submit comment. Please try again.', 'error');
            }
        } catch (error) {
            showMessage('Error submitting comment: ' + error.message, 'error');
        } finally {
            submitBtn.disabled = false;
            submitBtn.textContent = 'Submit Comment';
        }
    });

    function showMessage(text, type) {
        const messageDiv = document.getElementById('message');
        messageDiv.textContent = text;
        messageDiv.className = 'message ' + type;
    }
</script>
```

Possible Solution

The CORS middleware should intercept and handle OPTIONS requests before they reach the normal routing logic. Currently, it appears that preflight requests are being processed through the standard route matching, which results in a 404 when no explicit OPTIONS route is defined.

As a workaround, manually adding OPTIONS routes works:

app.options('/submit', async () => {
  return new Response(null, { status: 204 });
});

However, this shouldn't be necessary if the CORS middleware is functioning as intended.

Powertools for AWS Lambda (TypeScript) version

latest

AWS Lambda function runtime

22.x

Packaging format used

npm

Execution logs

Browser console error: Failed to load resource: Preflight response is not successful. Status code: 404


Note: I'm uncertain whether this is a bug in the middleware logic or a misunderstanding of the expected behavior. The issue might be related to the middleware not properly short-circuiting the request processing for preflight OPTIONS requests, but further investigation would be needed to confirm the root cause.

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingcompletedThis item is complete and has been merged/shippedevent-handlerThis item relates to the Event Handler Utility

Type

No type

Projects

Status

Shipped

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions