OPTIONS request always gets 405

Hello there,

I have a Caddyfile which 1) serves a PHP (Api-Platform) app and 2) handles mercure updates (compiled with xcaddy + mercure plugin), which basically looks like this:

my-hostname.local {

    tls internal

    route {
        root * /Users/me/Projects/my-project/public
        mercure {
            transport_url bolt:///Users/me/mercure.db
            publisher_jwt {env.JWT_SECRET_KEY} RS256
            subscriber_jwt {env.JWT_SECRET_KEY} RS256
            anonymous
            subscriptions
        }
        vulcain
        push
        php_fastcgi 127.0.0.1:9001
        encode zstd gzip
        file_server
    }

    @match_mercure {
        path /.well-known/mercure
    }

    header @match_mercure Access-Control-Allow-Origin "*"
    header @match_mercure Access-Control-Allow-Headers Authorization
    header @match_mercure Access-Control-Max-Age 3600
}

So far so good. Now that my Mercure client effectively sends an Authorization header (I didn’t need that before), browser yells “Response to preflight request doesn’t pass access control check: It does not have HTTP ok status.”. So I assume that it performs an OPTIONS request that it did not before. And indeed, OPTIONS /.well-known/mercure returns 405.

So, I wanted to allow it by adding the following snippet, but it just doesn’t work (still 405 returned when i cURL it), wherever I put that snippet into the existing config. Did I miss something?

@options {
    method OPTIONS
}
respond @options 204

Thanks!
Caddy 2.4.6 on MacOS M1

/cc @dunglas

I didn’t want to ping him first, since it looked to me that it was more a caddy-related issue, considering that I can’t set a directive to respond 204 on a path + method matcher, whether that directive comes before or after the mercure one.
Caddyfile documentation is not 100% clear to me about “when” caddy considers the request is fully matched and responded and won’t execute further (or previous) directives.
Any help appreciated :slight_smile:

Finally figured it out. Looks like route directives prevail above all others. Adding a mercure “dedicated” route solves the issue.

my-hostname.local {

    tls internal

    # Mercure-related stuff
    route /.well-known/mercure {
        header Access-Control-Allow-Origin "*"
        header Access-Control-Allow-Headers Authorization
        header Access-Control-Max-Age 3600

        @options {
            method OPTIONS
        }
        respond @options 204

        mercure {
            transport_url bolt:///Users/me/mercure.db
            publisher_jwt {env.JWT_SECRET_KEY} RS256
            subscriber_jwt {env.JWT_SECRET_KEY} RS256
            anonymous
            subscriptions
        }
    }

    # API related stuff
    route {
        root * /Users/me/Projects/my-project/public
        vulcain
        push
        php_fastcgi 127.0.0.1:9001
        encode zstd gzip
        file_server
    }
}
1 Like

Hmm. That seems strange.

Caddy sorts directives according to this defined order:

In the case of plugins (i.e. mercure, vulcain), those don’t have an order defined, so you either need to use the order global option to give them one, or use route to force the order to be the order you write it in your config.

I’d recommend writing it like this maybe:

{
	order mercure after respond
	order vulcain before respond
}

my-hostname.local {
	tls internal

	handle /.well-known/mercure {
		header {
			Access-Control-Allow-Origin *
			Access-Control-Allow-Headers Authorization
			Access-Control-Max-Age 3600
		}

		@options method OPTIONS
		respond @options 204

		mercure {
			transport_url bolt:///Users/me/mercure.db
			publisher_jwt {env.JWT_SECRET_KEY} RS256
			subscriber_jwt {env.JWT_SECRET_KEY} RS256
			anonymous
			subscriptions
		}
	}

	handle {
		root * /Users/me/Projects/my-project/public
		vulcain
		push
		php_fastcgi 127.0.0.1:9001
		encode zstd gzip
		file_server
	}
}

The advantage is that using handle blocks will allow the default directive order to be used while still defining an order for the plugins.

Also handle blocks are mutually exclusive, so only the first matching handle will execute. The route directive doesn’t have that property.

1 Like

This topic was automatically closed after 30 days. New replies are no longer allowed.