CORS preflight mysteriously failing with gorilla/handlers
Asked Answered
A

1

2

I am publishing a Golang API for my application through Heroku. Not able to get my webapp (Flutter/Dart stack) to actually get a successful response from my api. However I am able to get a successful response using curl commands from local. I've read through several posts about altering the Go mux server and adding the correct headers but this has not worked for me. I even see these headers returned back during my curl requests. Could really use some help as this is slowing me down.

essentially this is my main class which creates the server

import (
    "api/preventative_care"
    "api/user"
    "github.com/gorilla/handlers"
    "github.com/gorilla/mux"
    "log"
    "net/http"
    "os"
)

func main() {
    log.SetFlags(log.LstdFlags | log.Llongfile)
    router := mux.NewRouter()

    // Where ORIGIN_ALLOWED is like `scheme://dns[:port]`, or `*` (insecure)

    headersOk := handlers.AllowedHeaders([]string{"*"})
    methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "OPTIONS"})
    originsOk := handlers.AllowedOrigins([]string{"*"})

    router.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
        log.Println("Up and running!")
    })
    router.HandleFunc("/api/login", user.LoginHandler).Methods("GET")
    router.HandleFunc("/api/recommendations", preventative_care.RecommendationHandler).Methods("GET")

    var port = os.Getenv("PORT")
    log.Printf("Starting application on port %s\n", port)

    //log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), router))
    log.Fatal(http.ListenAndServe(":" + os.Getenv("PORT"), handlers.CORS(originsOk, headersOk, methodsOk)(router)))

}

And the dart code which calls this API looks like this:

    Map<String, String> headers = {
      "content-type": "application/json",
      "username": username,
      "password": password
    };

    final response = await http.get(Uri.parse(uri), headers: headers);

I'm hosting both the webapp and the API in 2 separate Heroku Dynos. When I hit the API from my local using curl I see below:

$ > curl -iXGET https://my-app-api.herokuapp.com/api/login -H "username:hello" -H "password:pizza"

HTTP/1.1 200 OK
Server: Cowboy
Connection: keep-alive
Content-Type: application/json
Date: Thu, 18 Nov 2021 23:39:56 GMT
Content-Length: 160
Via: 1.1 vegur

I thought I was supposed to see see the Header Access-Control-Allow-Origin: * added there but it's not yet I still get 200 success back. However when I try to use my Webapp to hit the API from a login screen using Google Chrome I see this error:

Access to XMLHttpRequest at 'https://my-app-api.herokuapp.com/api/login' from origin 'https://my-app-staging.herokuapp.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

No idea - it's like the header is being removed by Chrome or something maybe?

EDIT: I've also tried sending a preflight request using CURL and I am seeing the correct headers - however it's still showing me 4XX error.

$ > curl -H "Access-Control-Request-Method: GET" -H "Origin: https://my-app-staging.herokuapp.com" --head https://my-app-api.herokuapp.com/api/login

HTTP/1.1 405 Method Not Allowed
Server: Cowboy
Connection: keep-alive
Access-Control-Allow-Origin: *
Date: Fri, 19 Nov 2021 00:51:52 GMT
Via: 1.1 vegur

So now I'm REALLY not sure

Aesthetically answered 19/11, 2021 at 0:8 Comment(0)
E
4

TL;DR

gorilla/handlers doesn't (yet?) support the wildcard for Access-Control-Allow-Headers. You must specify all the allowed headers explicitly. In your case, instead of

handlers.AllowedHeaders([]string{"*"})

you should have

handlers.AllowedHeaders([]string{"content-type", "username", "password"})

More details

The Fetch standard added support (in the case of non-credentialed requests) for the wildcard in the Access-Control-Allow-Headers header back in 2016. Most modern browsers now support this feature.

However, gorilla/handlers doesn't seem to have caught up with the spec yet. If you inspect the source code for the handlers.AllowedHeaders function and for the *cors.ServeHTTP method, you'll see that there's no special handling of the "*" value: it's treated literally. As a result, the CORS middleware detects a mismatch between the request headers supplied by your preflight request (content-type, username, and password) and your allowed headers (*, taken literally) and responds with a 403 without even setting the Access-Control-Allow-Origin header, thereby causing the access-control check to fail.

Epineurium answered 19/11, 2021 at 12:9 Comment(3)
This did fix it for me - it's just a really confusing error from the browser because even though I've specified the Allowed-Origins * - it complains to me that Access-Control-Allow-Origin: * is missing when in fact it should mention something that the headers I tried to pass are not accepted by the server!Aesthetically
@Aesthetically In this case, gorilla/handlers's implementation arguably is to blame for your confusion. The browser was telling you the truth: the response to the preflight request did not contain any Access-Control-Allow-Origin header, because gorilla/handlers only sets that header further down the *cors.ServeHTTP method, once and only if all the request headers have been successfully checked against your allowed headers.Epineurium
Really great analysis there - didn't even consider that could be the case. ThanksAesthetically

© 2022 - 2024 — McMap. All rights reserved.