Skip to content

CORS Plugin: Access-Control-Allow-Methods header does not include default methods (GET, POST, HEAD) #5103

@morteza-69

Description

@morteza-69

In Ktor 3.3.0, the CORS plugin does not correctly include the default HTTP methods (GET, POST, HEAD) in the Access-Control-Allow-Methods response header.

Current implementation in CORSConfig builds the methods header like this:

val methodsListHeaderValue = methods.filterNot { it in CorsDefaultMethods } .map { it.value } .sorted() .joinToString(", ")

Since CorsDefaultMethods = { GET, POST, HEAD }, these methods are filtered out and not included in the final header.
As a result, browsers see an incomplete Access-Control-Allow-Methods header (often containing only OPTIONS), which breaks CORS preflight validation.

To Reproduce
Steps to reproduce the behavior:

Enable the CORS plugin in Ktor 3.3.0 and allow POST.

Send an OPTIONS preflight request from the browser.

Inspect the response headers.

Example output:

Access-Control-Allow-Methods: OPTIONS

Expected output:

Access-Control-Allow-Methods: GET, POST, HEAD, OPTIONS

Expected behavior
The Access-Control-Allow-Methods header should always include all configured methods, including the defaults (GET, POST, HEAD).

Proposed Fix
Replace the filtering logic with a distinct collection of methods:

val methodsListHeaderValue = methods.distinct() .map { it.value } .sorted() .joinToString(", ")

This ensures that GET, POST, HEAD are included in the header along with any additional methods.

Environment

Ktor version: 3.3.0

Module: ktor-server-cors

JVM: 17

OS: (e.g. Ubuntu 22.04 / Windows 11)

Additional context
This bug prevents correct CORS preflight handling in browsers, leading to blocked requests even when methods are allowed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions