skip to Main Content

I am using Golang with gin-gonic/gin web framework in my backend and with React axios in my frontend. I tried to solve it for two days already, but I still get the same error below:

CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

This error happens only when I try to send the PATCH request, so the one which requires the preflight OPTIONS request, but everything works as expected for GET and POST, which do not run any preflight checks.

Here is the code for my router configuration:

package main

import (
    "book_renting/api"
    "log"
    "net/http"

    "github.com/gin-contrib/sessions"
    "github.com/gin-contrib/sessions/cookie"
    "github.com/gin-gonic/contrib/cors"
    "github.com/gin-gonic/gin"
    _ "github.com/lib/pq"
)

func main() {

    router := gin.Default()
    store := cookie.NewStore([]byte("your-secret-key"))
    store.Options(sessions.Options{MaxAge: 60 * 60 * 24})

    router.Use(cors.Default())
    router.Use(sessions.Sessions("sessions", store))

    router.Use(func(c *gin.Context) {
        host := c.Request.Header.Get("Origin")
        c.Writer.Header().Set("Access-Control-Allow-Origin", host)
        c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
        c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
        c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS")
        if c.Request.Method == "OPTIONS" {
            log.Println("Handling OPTIONS request")
            c.AbortWithStatus(http.StatusNoContent)
            return
        }
        log.Println("Executing CORS middleware")
        c.Next()
    })

    router.POST("/login", api.HandleLogin)
    router.GET("/logout", api.HandleLogout)
    router.POST("/register", api.HandleRegister)
    router.GET("/getCookie", api.GetCookieSession)

    router.GET("/books", api.GetBooksAPI)
    router.GET("/books/:id", api.BookByIdAPI)
    router.PATCH("/rent/:id", api.RentBookAPI)
    router.PATCH("/return/:id", api.ReturnBookAPI)
    router.Run("localhost:3000")
}

And here is the frontend side:

import axios from 'axios'

const url = 'http://localhost:3000'

export const loginUser = async (credentials) => await axios.post(`${url}/login`, credentials, {withCredentials: true})
export const logoutUser = async () => await axios.get(`${url}/logout`, {withCredentials: true})
export const registerUser = () => axios.post(`${url}/register`)
export const fetchBooks = () => axios.get(`${url}/books`, { withCredentials: true })
export const fetchBookByID = (book_id) => axios.get(`${url}/books/${book_id}`, { withCredentials: true })
export const rentBook = (book_id) => axios.patch(`${url}/rent/${book_id}`, { withCredentials: true })
export const returnBook = (book_id) => axios.patch(`${url}/return/${book_id}`, { withCredentials: true })

I am quite certain that I set up the backend side properly, that it should return all necessary headers.

For example for the GET request the response headers look like this:

HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS
Access-Control-Allow-Origin: http://localhost:3001
Content-Type: application/json; charset=utf-8
Date: Sat, 10 Jun 2023 22:12:11 GMT
Content-Length: 495

While for the PATCH request attempt I do not have any response (not surprisingly) and the preflight response headers are:

HTTP/1.1 200 OK
Date: Sat, 10 Jun 2023 22:12:12 GMT
Content-Length: 0

Do you have any suggestions what could be the issue? After these two days I am already clueless. Thank you in advance!

I also tried to put headers:

c.Writer.Header().Set("Access-Control-Allow-Origin", host)
        c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
        c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
        c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS")

…again in the if statement:

if c.Request.Method == "OPTIONS" {
    log.Println("Handling OPTIONS request")
    c.AbortWithStatus(http.StatusNoContent)
    return
    }

But it didn’t help at all. In fact, this if statement is not executed when the preflight is being performed and I know from the console that the server is executing the OPTIONS request.

[GIN] 2023/06/11 - 00:12:13 | 200 |       7.708µs |       127.0.0.1 | OPTIONS  "/rent/2"

EDIT:

Here is the cURL command sending the PATCH request (so in fact here is the preflight OPTIONS request):

curl 'http://localhost:3000/return/2' 
  -X 'OPTIONS' 
  -H 'Accept: */*' 
  -H 'Accept-Language: en-US,en;q=0.9,pl-PL;q=0.8,pl;q=0.7' 
  -H 'Access-Control-Request-Headers: content-type' 
  -H 'Access-Control-Request-Method: PATCH' 
  -H 'Cache-Control: no-cache' 
  -H 'Connection: keep-alive' 
  -H 'Origin: http://localhost:3001' 
  -H 'Pragma: no-cache' 
  -H 'Referer: http://localhost:3001/' 
  -H 'Sec-Fetch-Dest: empty' 
  -H 'Sec-Fetch-Mode: cors' 
  -H 'Sec-Fetch-Site: same-site' 
  -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36' 
  --compressed

And the response to this request:

HTTP/1.1 200 OK
Date: Sun, 11 Jun 2023 01:22:57 GMT
Content-Length: 0

3

Answers


  1. While it seems that a 204 response would be appropriate here, the specification is a bit unclear, so you may need to return a 200 for the options request to make CORS work in all browsers.

    Login or Signup to reply.
  2. It turns out that you’re using the deprecated package github.com/gin-gonic/contrib/cors. You should use github.com/gin-contrib/cors instead. Here is a demo configuration to use github.com/gin-contrib/cors:

    package main
    
    import (
        "github.com/gin-contrib/cors"
        "github.com/gin-contrib/sessions"
        "github.com/gin-contrib/sessions/cookie"
        "github.com/gin-gonic/gin"
    )
    
    func main() {
        router := gin.Default()
    
        config := cors.DefaultConfig()
        config.AddAllowHeaders("Authorization")
        config.AllowCredentials = true
        config.AllowAllOrigins = false
        // I think you should whitelist a limited origins instead:
        //  config.AllowAllOrigins = []{"xxxx", "xxxx"}
        config.AllowOriginFunc = func(origin string) bool {
            return true
        }
        router.Use(cors.New(config))
    
        store := cookie.NewStore([]byte("your-secret-key"))
        store.Options(sessions.Options{MaxAge: 60 * 60 * 24})
        router.Use(sessions.Sessions("sessions", store))
    
        // routes below
    
        router.Run("localhost:3000")
    }
    

    The PATCH request header for some reason lacks the "Cookie" header, despite the fact that I use the withCredentials parameter.

    axios.patch(`${url}/rent/${book_id}`, { withCredentials: true })
    

    Here { withCredentials: true } is treated as the data, and there is not config. If you don’t have data to send to the server, you should write it like this:

    axios.patch(`${url}/rent/${book_id}`, null, { withCredentials: true })
    
    Login or Signup to reply.
  3. what you have done in the middleware seems enough and with no issues. since the CORS are applying to other methods.

    have you handled all the errors in this specific "api.ReturnBookAPI" handler?

    if a request got error and it wouldn’t return the gin response appropriately, the CORS headers wouldn’t be reflected in the response headers.

    Content-Length: 0 this field in your res shows there are no response body at all

    check the gin service log output, and ensure you have printed and checked all the potential errors.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search