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

Created on 8 Dec 2017  路  14Comments  路  Source: labstack/echo

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

wontfix

Most helpful comment

@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.

All 14 comments

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

I am having the same issue. Requests to the grouped endpoint are failing the preflight when they shouldn't be.

same issue , any news?

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

I too am having same issue. any workaround?

Me too. Any workaround?

Got this too

There seems to be an issue with echo's trailing slashes
If we do
curl -i -X OPTIONS http://localhost:8083/group/test/ (note the trailing slash)
on the first example we get the correct response.

There seems to be an issue with echo's trailing slashes
If we do
curl -i -X OPTIONS http://localhost:8083/group/test/ (note the trailing slash)
on the first example we get the correct response.

The solution is to use e.Pre(middleware.AddTrailingSlash()). That way your first example works regardless of the slash.

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

Adding

e.Pre(middleware.AddTrailingSlash()

Is not a fix for me.
I am happy to try and contribute, but i might need some pointers on where to start on the debugging front.

  • I can now confirm, when you pass cors in as a middleware to "GET", it doesnt get called.
  • As mentioned above, if cors is set on the highest level via Use, it works.

I am yet to understand where this goes wrong :)

What I can conclude, so far.

  • middleware support on the actual route, cant handle cors, as the cors middleware expects OPTIONS. Which if you are using GET or POST etc, it will never be.
  • cors middleware should be used on the highest level, the variable where you create the server from
e := echo.New()

At this level, it will always get called as part of the highest level middleware.

The simplest, just add it there and be happy. Anything else, will make you frustrated and not help.
The middleware, only adds to the headers, if a pre-flight has been requested.

I think where I went wrong and perhaps others, is the concept, that middleware via Use can be used at the top level and in groups etc, however this particular middleware only works correctly in one spot.

@vishr is that fair to say?

@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.
Was this page helpful?
0 / 5 - 0 ratings

Related issues

asdine picture asdine  路  3Comments

toorop picture toorop  路  4Comments

alexzorin picture alexzorin  路  3Comments

neutronstein picture neutronstein  路  3Comments

younisshah picture younisshah  路  4Comments