Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin'

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
like image 902
Vertislav Avatar asked Sep 05 '25 21:09

Vertislav


1 Answers

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 })
like image 74
Zeke Lu Avatar answered Sep 08 '25 12:09

Zeke Lu