So I've got this Go http handler that stores some POST content into the datastore and retrieves some other info in response. On the back-end I use:
func handleMessageQueue(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
if r.Method == "POST" {
c := appengine.NewContext(r)
body, _ := ioutil.ReadAll(r.Body)
auth := string(body[:])
r.Body.Close()
q := datastore.NewQuery("Message").Order("-Date")
var msg []Message
key, err := q.GetAll(c, &msg)
if err != nil {
c.Errorf("fetching msg: %v", err)
return
}
w.Header().Set("Content-Type", "application/json")
jsonMsg, err := json.Marshal(msg)
msgstr := string(jsonMsg)
fmt.Fprint(w, msgstr)
return
}
}
In my firefox OS app I use:
var message = "content";
request = new XMLHttpRequest();
request.open('POST', 'http://localhost:8080/msgs', true);
request.onload = function () {
if (request.status >= 200 && request.status < 400) {
// Success!
data = JSON.parse(request.responseText);
console.log(data);
} else {
// We reached our target server, but it returned an error
console.log("server error");
}
};
request.onerror = function () {
// There was a connection error of some sort
console.log("connection error");
};
request.send(message);
The incoming part all works along and such. However, my response is getting blocked. Giving me the following message:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:8080/msgs. This can be fixed by moving the resource to the same domain or enabling CORS.
I tried a lot of other things but there is no way I can just get a response from the server. However when I change my Go POST method into GET and access the page through the browser I get the data that I want so bad. I can't really decide which side goes wrong and why: it might be that Go shouldn't block these kinds of requests, but it also might be that my javascript is illegal.
#Egidius, when creating an XMLHttpRequest, you should use
var xhr = new XMLHttpRequest({mozSystem: true});
What is mozSystem?
mozSystem Boolean: Setting this flag to true allows making cross-site connections without requiring the server to opt-in using CORS. Requires setting mozAnon: true, i.e. this can't be combined with sending cookies or other user credentials. This only works in privileged (reviewed) apps; it does not work on arbitrary webpages loaded in Firefox.
Changes to your Manifest
On your manifest, do not forget to include this line on your permissions:
"permissions": {
"systemXHR" : {},
}
You need other headers, not only access-control-allow-origin.
If your request have the "Access-Control-Allow-Origin" header, you must copy it into the response headers, If doesn't, you must check the "Origin" header and copy it into the response. If your request doesn't have Access-Control-Allow-Origin not Origin headers, you must return "*".
You can read the complete explanation here: http://www.html5rocks.com/en/tutorials/cors/#toc-adding-cors-support-to-the-server
and this is the function I'm using to write cross domain headers:
func writeCrossDomainHeaders(w http.ResponseWriter, req *http.Request) {
// Cross domain headers
if acrh, ok := req.Header["Access-Control-Request-Headers"]; ok {
w.Header().Set("Access-Control-Allow-Headers", acrh[0])
}
w.Header().Set("Access-Control-Allow-Credentials", "True")
if acao, ok := req.Header["Access-Control-Allow-Origin"]; ok {
w.Header().Set("Access-Control-Allow-Origin", acao[0])
} else {
if _, oko := req.Header["Origin"]; oko {
w.Header().Set("Access-Control-Allow-Origin", req.Header["Origin"][0])
} else {
w.Header().Set("Access-Control-Allow-Origin", "*")
}
}
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
w.Header().Set("Connection", "Close")
}
You have to placed this code in application.rb
config.action_dispatch.default_headers = {
'Access-Control-Allow-Origin' => '*',
'Access-Control-Request-Method' => %w{GET POST OPTIONS}.join(",")
}
Related
Description
I have an API which I have created and I have some endpoints protected. The problem I am facing now on the client making a request is that the first request comes through with the Authorization header provided but a second request is blocked because Authorization is not present.
I can confirm that Authorization is present and worked perfectly when I was running Typescript till I recreated the endpoints in Go with Gin.
How to reproduce
Call estimate endpoint from client (iOS app) response succeceds
Make a second call from Client (iOS app) response failed because it is not taking the Authorization header which contains token
package main
import (
"github.com/gin-gonic/gin"
)
type App struct {
Router *gin.Engine
Gateman *gateman.Gateman
Database *mongo.Client
}
func (a *App) StartApp() {
err := godotenv.Load()
if err != nil {
fmt.Printf("Could not load .env \n")
}
a.Database = database.DB
a.Router = gin.New()
a.Gateman = middleware.Gateman()
a.Router.Use(gin.Recovery())
a.Router.Use(middleware.DefaultHelmet())
a.Router.Use(middleware.GinContextToContextMiddleware())
a.Router.Use(middleware.RequestID(nil))
a.Router.Use(middleware.ErrorHandler())
a.Router.Use(middleware.Logger("package-service"))
connection, err := amqp091.Dial(os.Getenv("AMQP_URL"))
if err != nil {
log.Fatal(fmt.Printf("Error on dial %v\n", err.Error()))
}
routes.Routes(a.Router, database.GetDatabase(a.Database), a.Gateman, connection)
}
func (a *App) Run(addr string) {
logs := log.New(os.Stdout, "package-service", log.LstdFlags)
server := &http.Server{
Addr: addr,
Handler: a.Router,
ErrorLog: logs,
IdleTimeout: 120 * time.Second, // max time for connections using TCP Keep-Alive
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
go func() {
if err := server.ListenAndServe(); err != nil {
logs.Fatal(err)
}
}()
// trap sigterm or interrupt and gracefully shutdown the server
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt)
signal.Notify(c, os.Kill)
sig := <-c
logs.Println("Recieved terminate, graceful shutdown", sig)
tc, _ := context.WithTimeout(context.Background(), 30*time.Second)
server.Shutdown(tc)
}
func Routes(r *gin.Engine, db *mongo.Database, g *gateman.Gateman, conn *amqp091.Connection) {
atHandler := pc.NewPackagesController(ps.NewPackagesService(pr.NewPackagesRepository(db)), g, events.NewEventEmitter(conn))
r.Use(CORS())
v1 := r.Group("/api/v1/package")
{
v1.POST("/query", GraphqlHandler(db, directives.NewDirectivesManager(g)))
v1.GET("/", PlaygroundHandler(db))
v1.POST("/", g.Guard([]string{"user"}, nil), atHandler.Create)
v1.POST("/estimate", g.Guard([]string{"user"}, nil), atHandler.Estimate)
v1.PUT("/:packageID", g.Guard([]string{"user", "admin"}, nil), atHandler.Update)
v1.PUT("/:packageID/assign", g.Guard([]string{"admin"}, nil), atHandler.Assign)
v1.POST("/:packageID/cancel", g.Guard([]string{"user", "admin"}, nil), atHandler.CancelRequest)
v1.POST("/:packageID/complete", g.Guard([]string{"admin"}, nil), atHandler.Complete)
v1.POST("/:packageID/reject", g.Guard([]string{"admin"}, nil), atHandler.RejectRequest)
v1.GET("/healthz", atHandler.GetHealth)
}
r.GET("/", atHandler.GetUP)
}
func CORS() 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, DELETE")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
}
}
func main() {
start := App{}
start.StartApp()
start.Run(":3009")
}
Expectations
All endpoints that are Guarded simply checks the header for Authorization and if provided in the request, it should be successful
Actual result
First request succeed /estimate
Second request / POST request fails to accept Authorization header
Also irrespective of what the first post request is, the second post request just never accept the Authorization header
Also need to mention that I do not have this issue with postman. Both request run independently but using another client for the request, gives this problem
Environment
go version: 1.19
gin version (or commit ref): v1.8.1
operating system: Mac and iOS mobile
Here is my client code
func request<T>(with builder: BaseRequest) -> AnyPublisher<T, APIError> where T: Codable {
request(with: builder, customDecoder: JSONDecoder())
}
func request<T>(with builder: BaseRequest, customDecoder: JSONDecoder) -> AnyPublisher<T, APIError> where T: Codable {
let encoding: ParametersEncoder = [.get, .delete].contains(builder.method) ? URLParameretersEncoder() : JSONParametersEncoder()
customDecoder.keyDecodingStrategy = .convertFromSnakeCase
var url: URL {
var components = URLComponents()
components.scheme = "https"
components.host = builder.baseUrl
components.path = "/api/v1" + builder.path
guard let url = components.url else {
preconditionFailure("Invalid URL components: \(components)")
}
return url
}
var urlRequest = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 46.0)
urlRequest.httpMethod = builder.method.rawValue
builder.headers.forEach { key, value in
urlRequest.setValue(value, forHTTPHeaderField: key)
}
if let token = tokenManager.token {
urlRequest.setValue("Bearer " + token, forHTTPHeaderField: "Authorization")
// urlRequest.setValue("ABC", forHTTPHeaderField: "testing123")
}
if let parameters = builder.parameters {
guard let encoded = try? encoding.encode(parameters: parameters, in: urlRequest) else {
fatalError()
}
urlRequest = encoded
}
self.log(request: urlRequest)
return URLSession.shared
.dataTaskPublisher(for: urlRequest)
.receive(on: DispatchQueue.main)
.mapError {_ -> APIError in
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
return .unknown
}
.flatMap { data, response -> AnyPublisher<T, APIError> in
guard let response = response as? HTTPURLResponse else {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
return Fail(error: APIError.invalidResponse).eraseToAnyPublisher()
}
self.log(response: response, data: data, error: nil)
if (200...299).contains(response.statusCode) {
return Just(data)
.decode(type: T.self, decoder: customDecoder)
// .map {
// print($0)
// return $0
// } //added
.mapError {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
return .decodingError(underlyingError: $0)
}
.eraseToAnyPublisher()
} else {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
if response.statusCode == 401 {
// Send notification to remove corrdinator and return to root
// rxNotificationCenter.post(.invalidToken)
}
guard let errorResponse = try? customDecoder.decode(BaseResponse.self, from: data) else {
return Fail(error: APIError.decodingError(underlyingError: NSError("Can't decode error"))).eraseToAnyPublisher()
}
return Fail(error: APIError.server(response: errorResponse))
.eraseToAnyPublisher()
}
}
.eraseToAnyPublisher()
}
protocol BaseRequest {
var baseUrl: String { get }
var path: String { get }
var headers: HTTPHeaders { get }
var method: HTTPRequestMethod { get }
var parameters: HTTPParameters { get }
}
public typealias HTTPHeaders = [String: String]
public typealias HTTPParameters = [String: Any]?
Another point, calling a single endpoint multiple time, works fine, calling a different one is where the header is rejected
I'm trying to integrate saml using crewjam library with an open-source app in go.
After authentication test using samltest.id, I want to be redirected to the home page.
I have tried several ways, but nothing works well, i'm using gorilla/mux router:
func login(w http.ResponseWriter, r *http.Request) {
s := samlsp.SessionFromContext(r.Context())
if s == nil {
return
}
sa, ok := s.(samlsp.SessionWithAttributes)
if !ok {
return
}
fmt.Fprintf(w, "Token contents, %+v!", sa.GetAttributes())
w.Header().Add("Location", "http://localhost:8080/")
w.WriteHeader(http.StatusFound)
}
I have also tested :
http.Redirect(w, r, "http://localhost:8080/", http.StatusFound)
Can someone help me please?
Thanks :)
Calling w.Write or writing into it using Fmt.Fprintf requires HTTP status code to be set before, otherwise it sets default StatusOK
Server.go
// If WriteHeader is not called explicitly, the first call to Write
// will trigger an implicit WriteHeader(http.StatusOK).
Setting the status code multiple times throws superfluous log.
Therefore, Your code is setting the HTTP status code to 200 (http.StatusOk), so the redirect after that is simply impossible.
Solution:
func login(w http.ResponseWriter, r *http.Request) {
s := samlsp.SessionFromContext(r.Context())
if s == nil {
return
}
sa, ok := s.(samlsp.SessionWithAttributes)
if !ok {
return
}
// this line is removed
// fmt.Fprintf(w, "Token contents, %+v!", sa.GetAttributes())
w.Header().Add("Location", "http://localhost:8080/")
w.WriteHeader(http.StatusFound)
// Or Simply
// http.Redirect(w, r, "http://localhost:8080/", http.StatusFound)
}
Try to send your headers before writing content.
And optionally use a relative Location
w.Header().Add("Location", "/")
w.WriteHeader(http.StatusFound)
fmt.Fprintf(w, "Token contents, %+v!", sa.GetAttributes())
In my Go API, I'm using gin, and I have one value set in my Access-Control-Allow-Origin header. If I have more than one, my react UI throws an error to the effect of The Access-Control-Allow-Origin header contains multiple values 'http://value1, http://value2', but only one is allowed.... I need to set multiple values. How do I do this?
The API is a reverse proxy, and here's the relevant code:
func proxy(c *gin.Context) {
var remote = "myUrl"
proxy := httputil.NewSingleHostReverseProxy(remote)
proxy.Director = func(req *http.Request) {
req.Header.Set("Authorization", "My Auth Values")
req.Host = remote.Host
req.URL.Scheme = remote.Scheme
req.URL.Host = remote.Host
}
proxy.ModifyResponse = addCustomHeader
proxy.ServeHTTP(c.Writer, c.Request)
}
func addCustomHeader(r *http.Response) error {
r.Header["Access-Control-Allow-Origin"] = []string{"value1"}
return nil
}
A CORS header can only contain a single value. If you want to implement your own CORS middleware, you need to work around that fact.
A simple CORS middleware will add the Access-Control-Allow-Origin header with the value of the specific address of the incoming request, usually taken from the Referer or Origin header. Typically, you match this against a list or map first, to see if it's in your allow list. If so, then the address of the request is added as allowed origin (as a single value).
A simple example could look like this
allowList := map[string]bool{
"https://www.google.com": true,
"https://www.yahoo.com": true,
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if origin := r.Header.Get("Origin"); allowList[origin] {
w.Header().Set("Access-Control-Allow-Origin", origin)
}
})
Since you are using the reverse proxy, you can access the request from the response.
mod := func(allowList map[string]bool) func(r *http.Response) error {
return func(r *http.Response) error {
if origin := r.Request.Header.Get("Origin"); allowList[origin] {
r.Header.Set("Access-Control-Allow-Origin", origin)
}
return nil
}
}
proxy := &httputil.ReverseProxy{
Director: func(r *http.Request) {
r.URL.Scheme = "https"
r.URL.Host = "go.dev"
r.Host = r.URL.Host
},
ModifyResponse: mod(allowList),
}
You only need a single value for each incoming request. The usual technique is to configure trusted origins on the server, eg:
trustedOrigins: [https://www.domain1.com, https://www.domain2.com]
Then check the runtime value of the origin header, which is sent by all modern browsers. If this is a trusted origin then add CORS headers:
Access-Control-Allow-Origin: https://www.domain2.com
A wildcard could be used but that is not recommended and also will not work as intended if you are also using credentialed requests (eg those with cookies).
I made a backend in go and deployed it using Google Cloud Run. Now I am trying to ping it from my website hosted locally, but then I get a CORS error like
type: "cors"
url: "https://abc.a.run.app/do-a"
redirected: false
status: 500
ok: false
statusText: ""
headers: Headers {}
body: (...)
bodyUsed: false
These are the headers I set in my http handler function in go.
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Origin", "http://localhost:3000")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
My handler function is routed like
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
http.HandleFunc("/do-a", endpoints.DoA)
err := http.ListenAndServe(":"+port, nil)
handle(err)
}
Please check this example from the official documentation:
// Package http provides a set of HTTP Cloud Functions samples.
package http
import (
"fmt"
"net/http"
)
// CORSEnabledFunctionAuth is an example of setting CORS headers with
// authentication enabled.
// For more information about CORS and CORS preflight requests, see
// https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request.
func CORSEnabledFunctionAuth(w http.ResponseWriter, r *http.Request) {
// Set CORS headers for the preflight request
if r.Method == http.MethodOptions {
w.Header().Set("Access-Control-Allow-Credentials", "true")
w.Header().Set("Access-Control-Allow-Headers", "Authorization")
w.Header().Set("Access-Control-Allow-Methods", "POST")
w.Header().Set("Access-Control-Allow-Origin", "https://example.com")
w.Header().Set("Access-Control-Max-Age", "3600")
w.WriteHeader(http.StatusNoContent)
return
}
// Set CORS headers for the main request.
w.Header().Set("Access-Control-Allow-Credentials", "true")
w.Header().Set("Access-Control-Allow-Origin", "https://example.com")
fmt.Fprint(w, "Hello World!")
}
From the code you posted I can not tell if you check for the preflight request and set the Access-Control-Allow-Methods header.
Brand new to Go.. Still obviously learning the syntax and the basics.. But I do have a specific goal in mind..
I'm trying to just get a simple server up on :8080 that can respond to both HTTP and socket.io (via /socket.io/ url), specificaly with CORS.
My code:
package main
import (
"log"
"net/http"
"github.com/rs/cors"
"github.com/googollee/go-socket.io"
)
func SayHelloWorld(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, World!"))
}
func main() {
c := cors.New(cors.Options{
AllowedOrigins: []string{"*"},
AllowCredentials: true,
})
server, err := socketio.NewServer(nil)
if err != nil {
log.Fatal(err)
}
server.On("connection", func(so socketio.Socket) {
log.Println("on connection")
so.Join("chat")
so.On("chat message", func(msg string) {
log.Println("emit:", so.Emit("chat message", msg))
so.BroadcastTo("chat", "chat message", msg)
})
so.On("disconnection", func() {
log.Println("on disconnect")
})
})
server.On("error", func(so socketio.Socket, err error) {
log.Println("error:", err)
})
http.Handle("/socket.io/", c.Handler(server))
http.HandleFunc("/", SayHelloWorld)
log.Println("Serving at localhost:8080...")
log.Fatal(http.ListenAndServe(":8080", nil))
}
On the client side I'm still seeing:
WebSocket connection to 'wss://api.domain.com/socket.io/?EIO=3&transport=websocket&sid=xNWd9aZvwDnZOrXkOBaC' failed: WebSocket is closed before the connection is established.
(index):1 XMLHttpRequest cannot load https://api.domain.com/socket.io/?EIO=3&transport=polling&t=1420662449235-3932&sid=xNWd9aZvwDnZOrXkOBaC. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://fiddle.jshell.net' is therefore not allowed access.
EDIT #1:
So I've been banging my head away trying to understand why I can't connect.. Came across an even more confusing piece of the puzzle?
https://gist.github.com/acoyfellow/167b055da85248c94fc4
The above gist is the code of my golang server + the browser code used to connect.. This code will send 30 HTTP GET requests per second to the backend, without connecting, upgrading, or giving any errors (client or server side).. it essentially DDOS's my own backend?
Someone, please someone tell me I'm doing something stupid.. This is quite the pickle :P
EDIT #2:
I can stop the "DDOS" by simply adjusting the trailing / on the URL of the socket.io endpoint in Go.. So: mux.Handle("/socket.io", server) to mux.Handle("/socket.io/", server) will now produce error messages and connection attempts with error responses of:
WebSocket connection to 'wss://api.domain.com/socket.io/?EIO=3&transport=websocket&sid=0TzmTM_QtF1TaS4exiwF' failed: Error during WebSocket handshake: Unexpected response code: 400 socket.io-1.2.1.js:2
GET https://api.domain.com/socket.io/?EIO=3&transport=polling&t=1420743204485-62&sid=0TzmTM_QtF1TaS4exiwF 400 (Bad Request)
So I gave up using googoolee's Socket.io implementation and went with gorilla's.
I checked out their examples: https://github.com/gorilla/websocket/tree/master/examples/chat
Checked out their docs: http://www.gorillatoolkit.org/pkg/websocket
-- Under Origin Considerations I found:
An application can allow connections from any origin by specifying a function that always returns true:
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true },
}
I added this CheckOrigin function to the conn.go file in their example, and was able to get a CORS socket server talking to a browser.
As a first adventure into Golang, this was frustrating and fun.. +1 to anyone else learning
Don't you mean http + ws or https + wss. If you remove a s from wss, you should be able to connect.
If you want tls for web socket (wss), then you need to http.ListenAndServeTLS.
It appears that CORS does not apply to WebSockets. Per this related question "With WebSocket, there is an "origin" header, which browser MUST fill with the origin of the HTML containing the JS that opens the WS connection."
As stated here:
Cross origin websockets with Golang
How about in your SayHelloWorld func, adding something like:
w.Header().Set("Access-Control-Allow-Origin", "*")
Or, possibly better:
if origin := r.Header.Get("Origin"); origin != "" {
w.Header().Set("Access-Control-Allow-Origin", origin)
}
I get the similar problerm with normal ajax call. It require more work in both front-end and backend. I belive most popular front-end libs liek JQuery or AngularJS handle these very well.
I see you're using the https://github.com/rs/cors package but you don't include the usage of that package, here is the implement with only Go std package:
type CrossOriginServer struct {}
func (s *CrossOriginServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
// you may need to add some more headers here
allowHeaders := "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization"
if origin := req.Header.Get("Origin"); validOrigin(origin) {
rw.Header().Set("Access-Control-Allow-Origin", origin)
rw.Header().Set("Access-Control-Allow-Methods", "POST, PUT, PATCH, GET, DELETE")
rw.Header().Set("Access-Control-Allow-Headers", allowHeaders)
}
if req.Method == "OPTIONS" {
return
}
// if you want, you can use gorilla/mux or any routing package here
mux := http.NewServeMux()
mux.Handle("/socket.io/", c.Handler(server))
mux.HandleFunc("/", SayHelloWorld)
mux.ServeHTTP(rw, req)
}
func validOrigin(origin string) bool {
allowOrigin := []string{
"http://localhost:8081",
"http://example.com"
}
for _, v := range allowOrigin {
if origin == v {
return true
}
}
return false
}
func main() {
// do you stuff
// ...
// ...
http.ListenAndServe(":8080", &CrossOriginServer{})
}