56

I'm using Go gin framework gin

func CORSMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Writer.Header().Set("Content-Type", "application/json")
        c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
        c.Writer.Header().Set("Access-Control-Max-Age", "86400")
        c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
        c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, X-Max")
        c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")

        if c.Request.Method == "OPTIONS" {
            c.AbortWithStatus(200)
        } else {
            c.Next()
        }
    }
}

I've got Status Code:200 OK, but nothing happens after OPTIONS request. It looks like I miss something, but I can't understand where am I wrong.

Can anybody help me?

Dracontis
  • 4,184
  • 8
  • 35
  • 50
qwertmax
  • 3,120
  • 2
  • 29
  • 42

9 Answers9

96

FWIW, this is my CORS Middleware that works for my needs.

func CORSMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
        c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
        c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
        c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT")

        if c.Request.Method == "OPTIONS" {
            c.AbortWithStatus(204)
            return
        }

        c.Next()
    }
}
Jack
  • 20,735
  • 11
  • 48
  • 48
  • Why 204? Why not 200? – qwertmax Apr 04 '15 at 06:38
  • 10
    HTTP NoContent coz duh! – Billcountry Jul 16 '20 at 04:13
  • 1
    Almost 6 years old and still great. Fixed my problem in no time. Yet I wonder why this is not part of the official documentation on github... –  Oct 14 '20 at 06:49
  • Thanks @Jack . Able to add this only at the root of the application. Not working only for group routes of the application for which i added some conditions based on the route url. – Gireesh Dec 03 '20 at 01:56
  • 4
    `c.Writer.Header().Set("Access-Control-Allow-Origin", "*")` this may cause browser get into cors issue, in that case need to specify specific origin instead of `*`. – Eric Apr 26 '21 at 04:46
  • [Not all `OPTIONS` requests are CORS-preflight requests](https://jub0bs.com/posts/2023-02-08-fearless-cors/#4-categorise-requests-correctly), though. Also, as pointed out by @Eric, you're ignoring the [wildcard exception](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#credentialed_requests_and_wildcards). – jub0bs Mar 07 '23 at 12:49
28

There is also official gin's middleware for handling CORS requests github.com/gin-contrib/cors.

You could install it using $ go get github.com/gin-contrib/cors and then add this middleware in your application like this: package main

import (
    "time"

    "github.com/gin-contrib/cors"
    "github.com/gin-gonic/gin"
)

func main() {
    router := gin.Default()
    // CORS for https://foo.com and https://github.com origins, allowing:
    // - PUT and PATCH methods
    // - Origin header
    // - Credentials share
    // - Preflight requests cached for 12 hours
    router.Use(cors.New(cors.Config{
        AllowOrigins:     []string{"https://foo.com"},
        AllowMethods:     []string{"PUT", "PATCH"},
        AllowHeaders:     []string{"Origin"},
        ExposeHeaders:    []string{"Content-Length"},
        AllowCredentials: true,
        AllowOriginFunc: func(origin string) bool {
            return origin == "https://github.com"
        },
        MaxAge: 12 * time.Hour,
    }))
    router.Run()
}
Dracontis
  • 4,184
  • 8
  • 35
  • 50
  • 4
    If you use `AllowOriginFunc` your origins defined in `AllowOrigins` will be ignored. `AllowOriginFunc is a custom function to validate the origin. It take the origin as argument and returns true if allowed or false otherwise. If this option is set, the content of AllowOrigins is ignored.` – drocha87 May 19 '21 at 17:29
  • Also, `AllowOriginFunc` should be avoided, as it may lead to Web cache poisoning. See https://github.com/go-chi/cors/issues/23 – jub0bs Aug 17 '23 at 17:33
13
func CORSMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {

        c.Header("Access-Control-Allow-Origin", "*")
        c.Header("Access-Control-Allow-Credentials", "true")
        c.Header("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
        c.Header("Access-Control-Allow-Methods", "POST,HEAD,PATCH, OPTIONS, GET, PUT")

        if c.Request.Method == "OPTIONS" {
            c.AbortWithStatus(204)
            return
        }

        c.Next()
    }
}

then use it

router = gin.New()  
router.Use(CORSMiddleware())
Jeff Sloyer
  • 4,899
  • 1
  • 24
  • 48
Rahul S
  • 131
  • 1
  • 3
13

I spent like an hour to get why some example from the internet works, and some doesn't. So I got the difference - line order is important, fristly you should use config and then declare your endpoints, but not the opposite way.

Works:

router := gin.Default()
router.Use(cors.Default())
router.GET("/ping", pong)
router.Run(":8082")

Doesn't work:

router := gin.Default()
router.GET("/ping", pong)
router.Use(cors.Default())
router.Run(":8082")
sergentum
  • 193
  • 1
  • 7
4

There is package https://github.com/rs/cors, that handles CORS requests in the right way. It has the examples for the popular routers including gin. That it:

package main

import (
    "net/http"

    "github.com/gin-gonic/gin"
    cors "github.com/rs/cors/wrapper/gin"
)

func main() {
    router := gin.Default()

    router.Use(cors.Default())
    router.GET("/", func(context *gin.Context) {
        context.JSON(http.StatusOK, gin.H{"hello": "world"})
    })

    router.Run(":8080")
}

In common case, you just add the default handling with router.Use(cors.Default()) to your middlewares in gin. It is enough.

Alexander I.Grafov
  • 1,370
  • 2
  • 16
  • 17
3

We created a minimal middleware.

import (
    "github.com/gin-gonic/gin"
    "net/http"
)

type optionsMiddleware struct {

}

func CreateOptionsMiddleware() *optionsMiddleware{
    return &optionsMiddleware{}
}

func (middleware *optionsMiddleware)Response(context *gin.Context){
    if context.Request.Method == "OPTIONS" {
        context.AbortWithStatus(http.StatusNoContent)
    }
}

and register it with gin middleware :

app  := gin.New()
app.Use(middleware.CreateOptionsMiddleware().Response).
    Use(next-middleware)......
Rahul Garg
  • 4,069
  • 1
  • 34
  • 31
3

It looks like I miss something, but I can't understand where am I wrong.

Because CORS is more complex a protocol than meets the eye, implementing it by "manually" setting CORS response headers is error-prone. In this specific case,

These difficulties may be enough to convince you that, in general, you're better off relying on a good CORS middleware library, which can abstract all this complexity away from you.

Gin does have an official CORS middleware library: gin-contrib/cors, but it's far from ideal; I've written at length about this topic in a recent blog post. If gin-contrib/cors leaves you dissatisfied, perhaps you'll appreciate my own CORS middleware library: jub0bs/fcors. It's designed to be, not only easier to use, but also harder to misuse.

It's primarily compatible with http.Handler, but it can be used in conjunction with Gin via a http.Handler-to-gin.HandleFunc adapter, such as that provided by gwatts/gin-adapter:

package main

import (
    "log"
    "net/http"

    "github.com/gin-gonic/gin"
    adapter "github.com/gwatts/gin-adapter"
    "github.com/jub0bs/fcors"
)

func main() {
    // create an engine
    engine := gin.Default()

    // configure the CORS middleware
    cors, err := fcors.AllowAccess(
        fcors.FromAnyOrigin(),
        fcors.WithMethods(
            http.MethodGet,
            http.MethodPost,
            http.MethodPut,
            http.MethodDelete,
            "UPDATE",
        ),
        fcors.WithRequestHeaders(
            "Authorization",
            "Content-Type",
            "X-CSRF-Token",
            "X-Max",
        ),
        fcors.MaxAgeInSeconds(86400),
    )
    if err != nil {
        log.Fatal(err)
    }

    // apply the CORS middleware to the engine
    engine.Use(adapter.Wrap(cors))

    // register the hello handler for the /hello route
    engine.GET("/hello", hello)

    // start the server on port 8080
    if err := engine.Run(":8080"); err != nil {
        log.Fatal(err)
    }
}

func hello(ctx *gin.Context) {
    ctx.JSON(http.StatusOK, gin.H{"hello": "world"})
}
jub0bs
  • 60,866
  • 25
  • 183
  • 186
2

This worked for me - NOTE: the setting of header directly.

func CORSMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {

        c.Header("Access-Control-Allow-Origin", "*")
        c.Header("Access-Control-Allow-Headers", "*")
        /*
            c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
            c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
            c.Writer.Header().Set("Access-Control-Allow-Headers", "access-control-allow-origin, access-control-allow-headers")
            c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, OPTIONS, PATCH")
        */

        if c.Request.Method == "OPTIONS" {
            c.AbortWithStatus(204)
            return
        }

        c.Next()
    }
}
2

With gin-contrib/cors

import "github.com/gin-contrib/cors"

[...]

router.Use(cors.New(cors.Config{
    AllowOrigins:     []string{"http://localhost:4040"},
    AllowMethods:     []string{"GET"},
    AllowHeaders:     []string{"Content-Type", "Content-Length", "Accept-Encoding", "Authorization", "Cache-Control"},
    ExposeHeaders:    []string{"Content-Length"},
    AllowCredentials: true,
    MaxAge:           12 * time.Hour,
}))

In my case, I had a JWT token middleware before this one that blocked all OPTION pre-flight requests. Putting the CORS middleware first solved the problem.

Michaël COLL
  • 1,153
  • 13
  • 18