Go gin framework CORS
Asked Answered
C

13

64

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?

Clone answered 2/4, 2015 at 17:3 Comment(0)
A
109

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()
    }
}
Amnion answered 3/4, 2015 at 21:1 Comment(6)
Why 204? Why not 200?Clone
HTTP NoContent coz duh!Eula
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...Lixiviate
Thanks @Amnion . 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.Packer
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 *.Warfield
Not all OPTIONS requests are CORS-preflight requests, though. Also, as pointed out by @Eric, you're ignoring the wildcard exception.Adlai
C
29

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()
}
Captain answered 13/2, 2018 at 9:31 Comment(1)
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.Everywhere
N
21

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")
Nonalignment answered 24/10, 2021 at 14:8 Comment(0)
C
14
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())
Conceit answered 9/9, 2020 at 11:58 Comment(0)
H
6

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.

Hexad answered 21/2, 2019 at 8:9 Comment(0)
T
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)......
Talon answered 13/11, 2017 at 17:51 Comment(1)
Careful. Not all OPTIONS requests are preflight requests.Adlai
O
3

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.

Ohmmeter answered 21/10, 2022 at 20:23 Comment(1)
Because Accept-Encoding and Content-Length are so-called forbidden request-headers (per the Fetch standard), listing them as allowed headers in your CORS configuration is useless.Adlai
A
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/cors. 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/cors"
)

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

    // configure the CORS middleware
    corsMw, err := cors.NewMiddleware(cors.Config{
        Origins: []string{"*"},
        Methods: []string{
            http.MethodGet,
            http.MethodPost,
            http.MethodPut,
            http.MethodDelete,
            "UPDATE",
        },
        RequestHeaders: []string{
            "Authorization",
            "Content-Type",
            "X-CSRF-Token",
            "X-Max",
        },
        MaxAgeInSeconds: 86400,
    })
    if err != nil {
        log.Fatal(err)
    }

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

    // 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"})
}
Adlai answered 7/3, 2023 at 16:49 Comment(0)
J
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()
    }
}
Jumbala answered 27/11, 2020 at 6:9 Comment(2)
This works for me too, that cors.Default() got me nowhere, tks.Valorie
Careful. Not all OPTIONS requests are preflight requests.Adlai
N
0

You cannot set c.Writer.Header().Set("Access-Control-Allow-Origin", "*") and c.Writer.Header().Set("Access-Control-Allow-Credentials", "true"). Either specify Access-Control-Allow-Origin to a specific domain or set Access-Control-Allow-Credentials to false.

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", "false")

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

or if the origin is http://localhost:3000

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", "http://localhost:3000")
        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(204)
        } else {
            c.Next()
        }
    }

Nansen answered 28/9, 2023 at 9:36 Comment(1)
The condition c.Request.Method == "OPTIONS" is too restrictive: not all OPTIONS requests are preflight requests. See jub0bs.com/posts/2023-02-08-fearless-cors/…Adlai
C
0

Adding to other answers using gin-contrib/cors - apparently DefaultConfig() is important.

What caused the same issue for me:

router.Use(cors.New(cors.Config{
    AllowOrigins: []string{
        "http://127.0.0.1:3000",
    },
}))

What worked for me:

corsConfig := cors.DefaultConfig()
corsConfig.AllowOrigins = []string{
    "http://127.0.0.1:3000",
}
router.Use(cors.New(corsConfig))
Chiffonier answered 27/2 at 3:40 Comment(0)
A
0

In my case, I am using a react app as "source of http calls". I tried to use many of suggestions in this thread, but apparently none of them worked.

Then I looked at the browser network develop tab, when then i realized that react sends an OPTION request, ant at request header there was 'Access-Control-Request-Headers: authorization,content-type,user-agent'

At this moment, i added "user-agent" in the response header "Access-Control-Allow-Headers", and then all worked. The "final" code was:

c.Header("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With,user-agent")

Apolitical answered 9/3 at 12:55 Comment(0)
C
0

WORKING!! Custom CORS Middleware in GIN

Note: You can add checks for method and headers as well

Please read this article for better understanding(https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)

func CORSMiddleware() gin.HandlerFunc {
    newConfig := config.NewInitConfig()
    originsString := newConfig.AllowedOrigins
    var allowedOrigins []string
    if originsString != "" {
        allowedOrigins = strings.Split(originsString, ",")
    }

    return func(c *gin.Context) {
        isOriginAllowed := func(origin string, allowedOrigins []string) bool {
            for _, allowedOrigin := range allowedOrigins {
                if origin == allowedOrigin {
                    return true
                }
            }
            return false
        }

        origin := c.Request.Header.Get(constants.OriginHeader)
        if isOriginAllowed(origin, allowedOrigins) {
            c.Writer.Header().Set(constants.AllowOriginHeader, origin)
            c.Writer.Header().Set(constants.AccessControlAllowCredentials, newConfig.AccessControlAllowCredentials)
            c.Writer.Header().Set(constants.AccessControlAllowHeaders, newConfig.AccessControlAllowHeaders)
            c.Writer.Header().Set(constants.AccessControlAllowMethods, newConfig.AccessControlAllowMethods)
        }

        if c.Request.Method == constants.OptionsMethod {
            c.AbortWithStatus(http.StatusNoContent)
            return
        }

        c.Next()
    }
}

This will return the request to the browser before reaching other middleware in your code

Cramfull answered 4/4 at 15:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.