We’ve all felt this as Go developers: You just spent hours perfecting your backend, and you’re eager to release your application’s next version. You add some new logic in your website’s code to test the functionality of your API and…nothing. So you dig into the logs as all good problem solvers do, and see something like:
Access to fetch at 'https://api.example.com' from origin 'https://example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present.
Now, before we start ripping apart our application one error at a time, we take a breath and zoom out. The main issue is your CORS, or Cross-Origin Resource Sharing, policy. We will delve deeper into what CORS is, but if you are encountering issues of this nature, this guide will show you exactly how to resolve them and ensure your CORS configuration is actually secure. A proper CORS fix will ensure your API remains functional for trusted clients while keeping malicious requests out.
Let’s begin by examining the foundation of CORS before delving into the specific fixes and security implications.
What Is CORS?
CORS stands for Cross-Origin Resource Sharing. It’s a security feature that governs how web pages from one origin are permitted to interact with resources hosted on a different origin.
When defining your URL, or Uniform Resource Locator, there are three major aspects to keep in mind:
- Protocol
- Hostname
- Port
This is what is known as the “origin”. An example of a full URL showcasing the “origin” would look like:
https://api.example.com:443
The origin groups a set of URLs under the control of the same individual or organization.
By default, browsers enforce the same-origin policy, which restricts websites from making HTTP requests to different origins. CORS provides a way to relax this restriction through HTTP headers that specify which cross-origin requests are allowed. When setting up your configuration, it’s crucial to know that it is defined at the server side (your Go application) and enforced at the browser level.
Understanding CORS Errors in Go Applications
If you’re reading this, you’ve likely encountered CORS errors when your frontend application tries to access your Go API. Here’s what these errors typically look like:
Request header field 'Authorization' is not allowed by Access-Control-Allow-Headers in preflight response.
Or:
Access to XMLHttpRequest at 'http://localhost:8080/api/data' from origin 'http://localhost:3000' has been blocked by CORS policy: Request header field authorization is not allowed by Access-Control-Allow-Headers in preflight response.
If you’re seeing these Access-Control-Allow errors appear in your app or console, CORS configuration troubleshooting is in your future.
Where CORS Issues Occur with Go
CORS issues are usually caused by the following:
- Frontend calling Go API: React/Vue/Angular app at
https://myapp.com
calling Go API athttps://api.myapp.com
- Development setup: Frontend dev server running at
http://localhost:3000
, calling your Go API athttp://localhost:8080
- SPA authentication: Single-page applications trying to send cookies or authentication headers
- Mobile app development: Testing APIs from different development environments
Fortunately, these issues are easy to resolve with proper configuration. Next, we’ll show you how to fix the errors at a core level.
How to Diagnose CORS Errors in Go
Like I said in the introduction, we want to make sure we don’t start completely ripping apart our application line by line. We could spend hours just going off the final line of the logs, when in actuality, it may have been fixed by checking the following off your diagnosis checklist:
- Check the Network tab – You’ll see the request, but no response data
- Look for preflight OPTIONS requests – Complex requests show an OPTIONS call before the actual request
- Test the API directly – Try the same endpoint with curl or Postman
If the API responds normally but fails in the browser, then your intuition and log sleuthing should be commended, because we have a CORS issue on our hands.
Do You Need CORS?
Because CORS is a security feature, your default strategy should be to enable it only when you’re sure that you need it, and only where necessary.
Not every cross-origin request requires CORS. Embedding an image, media file, iframe, CSS stylesheet, or JavaScript library from another domain isn’t subject to the same-origin policy. Only direct requests from scripts, such as API calls through fetch() or XMLHttpRequest, require CORS configuration.
We’ll look at two scenarios where you need CORS:
- Your Go website exposes a clearly defined (RESTful) API for others. You also expect your API consumers to make API calls directly on their websites through the user’s browser (instead of their servers).
- Your Go project exposes APIs or other endpoints only for internal use, but your website spans multiple domains or subdomains. A particular case of this is when you want to access the production backend while developing the frontend on localhost or a staging server.
In both cases, you should expose the minimum number of endpoints to foreign origins. If you don’t want others to use your APIs, make sure that they can’t.
If your website has neither of these use cases, then you don’t need CORS.
How to Enable CORS in Go
Now, let’s see how you enable CORS in Go. There are several approaches, from simple manual header setting to using middleware packages.
Manual CORS Implementation
The simplest approach is to manually set the CORS headers in your HTTP handlers. Here’s a basic example:
func enableCors(w http.ResponseWriter) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
}
func handleArticles(w http.ResponseWriter, r *http.Request) {
enableCors(w)
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusOK)
return
}
articles := []Article{
{ID: "1", Title: "First article", Desc: "Description", Content: "Content"},
{ID: "2", Title: "Second article", Desc: "Description", Content: "Content"},
}
w.Header().Set("Content-Type", "application/json")
if js, err := json.Marshal(articles); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
} else {
w.Write(js)
}
}
Note: Wildcard origin (see more later in this guide) is not suitable for production. Specifying the origin explicitly, rather than for all possible origins, is recommended.
Using Middleware for Better Organization
Developers have proven that you can code a solution to nearly anything, so you can, of course, create your own CORS middleware. Here is a simple example of how this could be done:
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/articles", handleArticles)
// Apply CORS middleware
handler := corsMiddleware(mux)
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", handler))
}
Using Third-Party CORS Libraries
An even better approach is to consider using established CORS libraries like github.com/rs/cors:
import (
"github.com/rs/cors"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/articles", handleArticles)
// Configure CORS
c := cors.New(cors.Options{
AllowedOrigins: []string{"http://localhost:3000", "https://myapp.com"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"*"},
AllowCredentials: true,
})
handler := c.Handler(mux)
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", handler))
}
Securing Your CORS Configuration
For Development
During development and to reduce the headache of manually checking different origins, it is ok to allow requests from your local development server:
func enableCors(w *http.ResponseWriter) {
(*w).Header().Set("Access-Control-Allow-Origin", "http://localhost:3000")
(*w).Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
(*w).Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
}
For Production
In production, it is crucial to specify the exact origins that should have access. This is the one thing StackHawk scans for: vulnerabilities. Therefore, ensure that you don’t ship a potentially vulnerable application. Production-level code may look like:
func enableCors(w *http.ResponseWriter, r *http.Request) {
allowedOrigins := []string{
"https://myapp.com",
"https://www.myapp.com",
"https://admin.myapp.com",
}
origin := r.Header.Get("Origin")
for _, allowed := range allowedOrigins {
if origin == allowed {
(*w).Header().Set("Access-Control-Allow-Origin", origin)
break
}
}
(*w).Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
(*w).Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
}
Common CORS Configuration Options
Here are the key CORS headers you’ll work with in Go:
- Access-Control-Allow-Origin: Specifies which origins can access the resource
- Access-Control-Allow-Methods: Lists the HTTP methods allowed for cross-origin requests
- Access-Control-Allow-Headers: Specifies which headers can be used in the actual request
- Access-Control-Allow-Credentials: Indicates whether credentials (cookies, authorization headers) can be sent
- Access-Control-Max-Age: Specifies how long preflight results can be cached
Why Automated CORS Testing Matters
CORS misconfigurations are among the most common security issues in modern web applications, but they are often overlooked. It’s easy to accidentally create security vulnerabilities while attempting to resolve functionality issues, such as allowing credentials with a wildcard origin (Access-Control-Allow-Origin: *
) or trusting overly broad origin patterns.
This is where StackHawk comes in. As a Dynamic Application Security Testing (DAST) tool, StackHawk helps engineers debug and locate security vulnerabilities at every step of the development process. By using StackHawk’s DAST capabilities to test for vulnerabilities in your Go applications and APIs, you get:
- Early detection of CORS issues during development
- Automated testing of complex CORS scenarios you might not think to test manually
- Clear remediation guidance with specific examples of vulnerable requests
- Verification that fixes don’t introduce new security issues
Validating Your Go CORS Configuration with StackHawk
Once you’ve implemented CORS headers in your Go application, it’s crucial to ensure your configuration is both functional and secure. Instead of manually testing every possible CORS scenario, you can use StackHawk to automatically validate that your CORS implementation works correctly without creating new attack vectors.
Instead of waiting for your security team to discover these issues later in development, let’s use StackHawk to automatically identify potential vulnerabilities for us. To do this, you’ll need to ensure you have a StackHawk account. If you need one, you can sign up for a trial account or log into an existing account.
If you’re logging into an existing StackHawk account, from the Applications screen, you’ll click Add Application.
If you’re new to StackHawk, you’ll be automatically brought into the Add an App flow. On the Scanner screen, you’ll see the instructions for installing the StackHawk CLI. Since we will be running our testing locally, we will use this option. Once the hawk init
command is executed successfully, click the Next button.
On the next screen, you will fill out an Application Name, Environment, and URL. Once filled out, click Next.
Since we will be testing a Golang application with API endpoints, on the next page, we will choose our Application Type as “API”. Depending on your Golang API setup, you’ll also configure the API details. For example, if you have a REST API running, you would set the API Type to “REST / OpenAPI” and point to your OpenAPI specification file by selecting either URL Path or File Path, and then adding the path to where your OpenAPI spec is located. Alternatively, you can click “Skip for now” if you’re unsure. Once complete, click Next.
Lastly, we will need to add a stackhawk.yml
file to the root of our Golang project. Once the file is added, copy the screen’s contents, paste them into the file, and save it. Lastly, we can click the Finish button.
In our root directory, you should see the stackhawk.yml
file we’ve added:
Lastly, depending on our scan policy, CORS misconfiguration detection may not be enabled. To ensure this, go back to StackHawk, navigate to the Applications page, and select your app. Once on the configuration page, click on the Settings tab, then the Active Scan Plugins tab, located just below it. Then, locate the CORS Header plugin in the list and check it off. You can find it easily by typing “cors” into the plugin search bar.
Lastly, click Save, located just above the Plugins list, to save the new configuration.
Run HawkScan
Next, we can proceed with testing our Go application. In a terminal pointing to the root of our project, we will run HawkScan using the following command:
hawk scan
After running the command, the tests should execute in the terminal.
Note: If you get an error similar to:
HawkScan Target Not Found Error: Unable to access https://localhost:4000. Check if the web server is listening on the specified port.
This means that your Golang application is not running in HTTPS, and that is how HawkScan is trying to call the API. To fix this, either add HTTPS capabilities to your Golang app or, more simply, change the host entry in your stackhawk.yml
to use “http”.
This will run tests against your Golang application and its API endpoints. Once the tests have run, we can begin to explore any findings that were discovered.
Explore The Initial Findings
Once the tests are complete, the terminal will contain some information about any vulnerabilities found. You may notice that StackHawk has identified CORS vulnerabilities within your Golang application, which are present on multiple API endpoints.
To explore this further, we will click on the test link at the bottom of the output. This will take us into the StackHawk platform to explore further.
After clicking on the link, we can now view the test results in a nicely formatted display. Next, we will click on the CORS Misconfiguration entry.
Within this entry, we can see an Overview and Resources that can help us with fixing this vulnerability, as well as the Request and Response that the API returned on the right side of the screen. Above this, you will also see a Validate button, which will display a cURL command with the exact HTTP request used to expose the vulnerability.
Understand and Fix the Golang CORS Security Issue
When StackHawk identifies CORS vulnerabilities, you’ll see detailed findings that indicate which paths are affected and provide potential remediation techniques (as shown in the image above). Using the techniques in this guide or the remediation advice provided by StackHawk for the vulnerability, you can then make the necessary adjustments to your code and configuration. Once fixed, ensure that you stop your web servers and redeploy the latest code. Next, we can ensure that the fix implemented actually resolves our CORS misconfiguration issues and is secure.
Confirm the Fix!
With the latest configuration deployed, let’s confirm the fix in StackHawk. To do this, we will click the Rescan Findings button in StackHawk.
Then, we will see a modal containing the “hawk rescan” command that includes the correct Scan ID. You’ll run this command in the same terminal where you ran the initial set of tests.
In the output, you will again see any vulnerabilities that were found during the scan. In this case, you’ll see that the CORS misconfiguration vulnerabilities are no longer showing. Clicking on the link at the bottom of the terminal output, you can confirm that the CORS misconfiguration vulnerabilities have now been added to the Fixed Findings from Previous Scan, confirming that the vulnerability has been successfully fixed and has passed any vulnerability tests.
With that, we’ve successfully remedied and retested our Golang application to ensure its safety from potential CORS misconfiguration attacks.
Conclusion
Implementing CORS in Go requires striking a balance between functionality and security. While Go provides full control over CORS implementation through HTTP headers, it’s essential to understand the security implications of each setting.
Key takeaways:
- Only enable CORS where you actually need it
- Use specific origins instead of wildcards when possible
- Always handle preflight OPTIONS requests properly
- Test your configuration thoroughly in both development and production environments
- Regularly audit your CORS settings for security vulnerabilities
Remember that fixing CORS errors correctly means ensuring your API works for legitimate requests while blocking potential security threats. Tools like StackHawk help automate this security validation, giving you confidence that your Go CORS implementation is both functional and secure.
Want to ensure your Golang CORS configuration is secure? Sign up for StackHawk today for a free 14-day trial to automatically test your CORS configuration and detect potential vulnerabilities.