-
Notifications
You must be signed in to change notification settings - Fork 155
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
issues/158/examples for working api with javascript frontend (#162)
Fixes #158, which is essentially that 1. none of the examples in the README for working with a JavaScript frontend will work without proper CORS config on the backend 2. there is no example at all for using the HTTP header instead of getting the CSRF token from the hidden form field **Summary of Changes** I have merged/copied over these simplified examples from my own repository of working examples. I was not sure how the maintainers may want to reference these examples in the main README. Copying them over to the README verbatim would be putting a lot of code into the README, but without changing the current README, the content there differs significantly from the examples. --------- Co-authored-by: Corey Daley <[email protected]>
- Loading branch information
1 parent
73c96a5
commit c1f4eb3
Showing
10 changed files
with
269 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# API Backends | ||
|
||
Examples in this directory are intended to provide basic working backend CSRF-protected APIs, | ||
compatible with the JavaScript frontend examples available in the | ||
[`examples/javascript-frontends`](../javascript-frontends). | ||
|
||
In addition to CSRF protection, these backends provide the CORS configuration required for | ||
communicating the CSRF cookies and headers with JavaScript client code running in the browser. | ||
|
||
See [`examples/javascript-frontends`](../javascript-frontends/README.md) for details on CORS and | ||
CSRF configuration compatibility requirements. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// +build ignore | ||
|
||
module github.com/gorilla-mux/examples/api-backends/gorilla-mux | ||
|
||
go 1.20 | ||
|
||
require ( | ||
github.com/gorilla/csrf v1.7.1 | ||
github.com/gorilla/handlers v1.5.1 | ||
github.com/gorilla/mux v1.8.0 | ||
) | ||
|
||
require ( | ||
github.com/felixge/httpsnoop v1.0.3 // indirect | ||
github.com/gorilla/securecookie v1.1.1 // indirect | ||
github.com/pkg/errors v0.9.1 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= | ||
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= | ||
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= | ||
github.com/gorilla/csrf v1.7.1 h1:Ir3o2c1/Uzj6FBxMlAUB6SivgVMy1ONXwYgXn+/aHPE= | ||
github.com/gorilla/csrf v1.7.1/go.mod h1:+a/4tCmqhG6/w4oafeAZ9pEa3/NZOWYVbD9fV0FwIQA= | ||
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= | ||
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= | ||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= | ||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= | ||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= | ||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= | ||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= | ||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
// +build ignore | ||
|
||
package main | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"net/http" | ||
"os" | ||
"strings" | ||
"time" | ||
|
||
"github.com/gorilla/csrf" | ||
"github.com/gorilla/handlers" | ||
"github.com/gorilla/mux" | ||
) | ||
|
||
func main() { | ||
router := mux.NewRouter() | ||
|
||
loggingMiddleware := func(h http.Handler) http.Handler { | ||
return handlers.LoggingHandler(os.Stdout, h) | ||
} | ||
router.Use(loggingMiddleware) | ||
|
||
CSRFMiddleware := csrf.Protect( | ||
[]byte("place-your-32-byte-long-key-here"), | ||
csrf.Secure(false), // false in development only! | ||
csrf.RequestHeader("X-CSRF-Token"), // Must be in CORS Allowed and Exposed Headers | ||
) | ||
|
||
APIRouter := router.PathPrefix("/api").Subrouter() | ||
APIRouter.Use(CSRFMiddleware) | ||
APIRouter.HandleFunc("", Get).Methods(http.MethodGet) | ||
APIRouter.HandleFunc("", Post).Methods(http.MethodPost) | ||
|
||
CORSMiddleware := handlers.CORS( | ||
handlers.AllowCredentials(), | ||
handlers.AllowedOriginValidator( | ||
func(origin string) bool { | ||
return strings.HasPrefix(origin, "http://localhost") | ||
}, | ||
), | ||
handlers.AllowedHeaders([]string{"X-CSRF-Token"}), | ||
handlers.ExposedHeaders([]string{"X-CSRF-Token"}), | ||
) | ||
|
||
server := &http.Server{ | ||
Handler: CORSMiddleware(router), | ||
Addr: "localhost:8080", | ||
ReadTimeout: 60 * time.Second, | ||
WriteTimeout: 60 * time.Second, | ||
} | ||
|
||
fmt.Println("starting http server on localhost:8080") | ||
log.Panic(server.ListenAndServe()) | ||
} | ||
|
||
func Get(w http.ResponseWriter, r *http.Request) { | ||
w.Header().Add("X-CSRF-Token", csrf.Token(r)) | ||
w.WriteHeader(http.StatusOK) | ||
} | ||
|
||
func Post(w http.ResponseWriter, r *http.Request) { | ||
w.WriteHeader(http.StatusOK) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# JavaScript Frontends | ||
|
||
Examples in this directory are intended to provide basic working frontend JavaScript, compatible | ||
with the API backend examples available in the [`examples/api-backends`](../api-backends). | ||
|
||
## CSRF and CORS compatibility | ||
|
||
In order to be compatible with a CSRF-protected backend, frontend clients must: | ||
|
||
1. Be served from a domain allowed by the backend's CORS Allowed Origins configuration. | ||
1. `http://localhost*` for the backend examples provided | ||
2. An example server to serve the HTML and JavaScript for the frontend examples from localhost is included in | ||
[`examples/javascript-frontends/example-frontend-server`](../javascript-frontends/example-frontend-server) | ||
3. Use the HTTP headers expected by the backend to send and receive CSRF Tokens. | ||
The backends configure this as the Gorilla `csrf.RequestHeader`, | ||
as well as the CORS Allowed Headers and Exposed Headers. | ||
1. `X-CSRF-Token` for the backend examples provided | ||
2. Note that some JavaScript HTTP clients automatically lowercase all received headers, | ||
so the values must be accessed with the key `"x-csrf-token"` in the frontend code. |
10 changes: 10 additions & 0 deletions
10
examples/javascript-frontends/example-frontend-server/go.mod
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
module github.com/gorilla-mux/examples/javascript-frontends/example-frontend-server | ||
|
||
go 1.20 | ||
|
||
require ( | ||
github.com/gorilla/handlers v1.5.1 | ||
github.com/gorilla/mux v1.8.0 | ||
) | ||
|
||
require github.com/felixge/httpsnoop v1.0.3 // indirect |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= | ||
github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= | ||
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= | ||
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= | ||
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= | ||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= | ||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= |
42 changes: 42 additions & 0 deletions
42
examples/javascript-frontends/example-frontend-server/main.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// +build ignore | ||
|
||
package main | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"net/http" | ||
"os" | ||
"time" | ||
|
||
"github.com/gorilla/handlers" | ||
"github.com/gorilla/mux" | ||
) | ||
|
||
func main() { | ||
router := mux.NewRouter() | ||
|
||
loggingMiddleware := func(h http.Handler) http.Handler { | ||
return handlers.LoggingHandler(os.Stdout, h) | ||
} | ||
router.Use(loggingMiddleware) | ||
|
||
wd, err := os.Getwd() | ||
if err != nil { | ||
log.Panic(err) | ||
} | ||
// change this directory to point at a different Javascript frontend to serve | ||
httpStaticAssetsDir := http.Dir(fmt.Sprintf("%s/../frontends/axios/", wd)) | ||
|
||
router.PathPrefix("/").Handler(http.FileServer(httpStaticAssetsDir)) | ||
|
||
server := &http.Server{ | ||
Handler: router, | ||
Addr: "localhost:8081", | ||
ReadTimeout: 60 * time.Second, | ||
WriteTimeout: 60 * time.Second, | ||
} | ||
|
||
fmt.Println("starting http server on localhost:8081") | ||
log.Panic(server.ListenAndServe()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="UTF-8"> | ||
<title>Gorilla CSRF</title> | ||
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> | ||
</head> | ||
<body> | ||
<div> | ||
<h1>Gorilla CSRF: Axios JS Frontend</h1> | ||
<p>See Console and Network tabs of your browser's Developer Tools for further details</p> | ||
</div> | ||
|
||
<div> | ||
<h2>Get Request:</h2> | ||
<h3>Full Response:</h3> | ||
<code id="get-request-full-response"></code> | ||
<h3>CSRF Token:</h3> | ||
<code id="get-response-csrf-token"></code> | ||
</div> | ||
|
||
|
||
<div> | ||
<h2>Post Request:</h2> | ||
<h3>Full Response:</h3> | ||
<p> | ||
Note that the <code>X-CSRF-Token</code> value is in the Axios <code>config.headers</code>; | ||
it is not a response header set by the server. | ||
</p> | ||
<code id="post-request-full-response"></code> | ||
</div> | ||
</body> | ||
<script src="index.js"></script> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
// make GET request to backend on page load in order to obtain | ||
// a CSRF Token and load it into the Axios instance's headers | ||
// https://github.com/axios/axios#creating-an-instance | ||
const initializeAxiosInstance = async (url) => { | ||
try { | ||
let resp = await axios.get(url, {withCredentials: true}); | ||
console.log(resp); | ||
document.getElementById("get-request-full-response").innerHTML = JSON.stringify(resp); | ||
|
||
let csrfToken = parseCSRFToken(resp); | ||
console.log(csrfToken); | ||
document.getElementById("get-response-csrf-token").innerHTML = csrfToken; | ||
|
||
return axios.create({ | ||
// withCredentials must be true to in order for the browser | ||
// to send cookies, which are necessary for CSRF verification | ||
withCredentials: true, | ||
headers: {"X-CSRF-Token": csrfToken} | ||
}); | ||
} catch (err) { | ||
console.log(err); | ||
} | ||
}; | ||
|
||
const post = async (axiosInstance, url) => { | ||
try { | ||
let resp = await axiosInstance.post(url); | ||
console.log(resp); | ||
document.getElementById("post-request-full-response").innerHTML = JSON.stringify(resp); | ||
} catch (err) { | ||
console.log(err); | ||
} | ||
}; | ||
|
||
// general-purpose func to deal with clients like Axios, | ||
// which lowercase all headers received from the server response | ||
const parseCSRFToken = (resp) => { | ||
let csrfToken = resp.headers[csrfTokenHeader]; | ||
if (!csrfToken) { | ||
csrfToken = resp.headers[csrfTokenHeader.toLowerCase()]; | ||
} | ||
return csrfToken | ||
} | ||
|
||
const url = "http://localhost:8080/api"; | ||
const csrfTokenHeader = "X-CSRF-Token"; | ||
initializeAxiosInstance(url) | ||
.then(axiosInstance => { | ||
post(axiosInstance, url); | ||
}); |