echo: Preflight/OPTIONS request return 405 for groups with cors/middleware

The 405 I am seeing happens when:

  • Making a preflight/options request to an endpoint
  • This endpoint is part of a group
  • This group has cors/middleware attached
  • The main route does not have cors/middleware

I want to acknowledge that this is very similar to https://github.com/labstack/echo/issues/228. However, it seems recently other people have commented that they are experiencing a similar issue and did not see a response.

To reproduce, example when CORS is added to a group only and OPTIONS returns 405

e := echo.New()
e.Use(middleware.Logger())

g := e.Group("/group")
g.Use(middleware.CORSWithConfig(middleware.CORSConfig{
    AllowOrigins:     []string{"*"},
    AllowHeaders:     []string{"authorization", "Content-Type"},
    AllowCredentials: true,
    AllowMethods:     []string{echo.OPTIONS, echo.GET, echo.HEAD, echo.PUT, echo.PATCH, echo.POST, echo.DELETE},
}))

g.GET("/test", func(c echo.Context) error {
    return c.String(http.StatusOK, "200")
})

curl -i -X OPTIONS http://localhost:8083/group/test

HTTP/1.1 405 Method Not Allowed
Content-Type: application/json; charset=UTF-8
Date: Fri, 08 Dec 2017 22:06:02 GMT
Content-Length: 32

However, if the CORS is added at the main route level (this is not what we want to do), OPTIONS returns ideal response:

e := echo.New()
e.Use(middleware.Logger())

e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
    AllowOrigins:     []string{"*"},
    AllowHeaders:     []string{"authorization", "Content-Type"},
    AllowCredentials: true,
    AllowMethods:     []string{echo.OPTIONS, echo.GET, echo.HEAD, echo.PUT, echo.PATCH, echo.POST, echo.DELETE},
}))

g := e.Group("/group")

g.GET("/test", func(c echo.Context) error {
    return c.String(http.StatusOK, "200")
})

curl -i -X OPTIONS http://localhost:8083/group/test

HTTP/1.1 204 No Content
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: authorization,Content-Type
Access-Control-Allow-Methods: OPTIONS,GET,HEAD,PUT,PATCH,POST,DELETE
Access-Control-Allow-Origin: *
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Date: Fri, 08 Dec 2017 22:05:49 GMT

Ideally, it would be nice when adding CORS at the group level only (see code in first example), that a 204 and all of the access-control headers is returned for preflight/options requests (see curl response in second example).

Note: using Echo version 3

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 9
  • Comments: 16 (1 by maintainers)

Most upvoted comments

@freshteapot This is my understanding as well. There are basically two distinct types of middleware in Echo:

  1. Top-level middlewares, i.e. e.Use(m...), are applied to every request, regardless of routing.
  2. Route-level middlewares, e.g. e.GET(path, h, m...), are only applied to matching routes.

Despite the similarity between e.Use(m...) and g.Use(m...), groups are actually just syntactic convenience for route-level middlewares. And the CORS middleware doesn’t work correctly at route level, because it relies on intercepting OPTIONS requests for which no handler has been defined.

My workaround has been to just declare a dummy OPTIONS handler for every route where I know I may need CORS. As long as some handler is defined, group middlewares for OPTIONS requests will be applied, allowing the CORS middleware to intercept the request and return the correct response. So it looks something like this:

g := e.Group("")
g.Use(middleware.CORSWithConfig(...))  // CORS middleware applied to group
g.GET("/foo", myHandler)  // Normal handler
g.OPTIONS("/foo", echo.MethodNotAllowedHandler)  // Dummy handler

If you have lots of routes, you can define a new type that wraps echo.Group and sets the dummy handler for you:

type CORSGroup struct {
    g *echo.Group
}
func (cg *CORSGroup) GET(path string, h echo.HandlerFunc, m ...echo.MiddlewareFunc) {
    cg.g.GET(path, h, m...)  // Normal handler
    cg.g.OPTIONS(path, echo.MethodNotAllowedHandler)  // Dummy handler
}
// POST, DELETE, etc.

Hello, just wanted to follow up on this issue. Is this a valid concern? Or is there a recommended alternative approach? Thank you.