Getting 'Forbidden - CSRF token invalid' while sending Ajax POST request using javascript XMLHttpRequest()
Kenmobility opened this issue · 19 comments
Hello, please I need help am sending many Ajax requests on my application and am making use of csrf, but some of the ajax requests returns 'Forbidden - CSRF token invalid' while some goes through without any issues and I have made sure that the javascript script code are the same on all the requests only changing the handler that each calls but still can't figure out while some don't go through, the ones am using through hidden input on forms goes through as well without any issue, my code snippet is below.
var crsfValue = document.getElementsByTagName('meta')['gorilla.csrf.Token'].getAttribute('content');
var xhr = new XMLHttpRequest();
xhr.open('POST', '/Device/UnLock');
xhr.setRequestHeader('X-CSRF-Token',crsfValue);
xhr.send(clicked);
Meanwhile, I always load the token using a meta tag on the head of my master page
@Kenmobility - can you share more details?
- example requests (both passing & failing) from the Network tab of the web inspector in your browser?
- Whether these are cross-origin requests or not?
- Whether the cookie is being sent on all requests?
- How you are injecting the token into the page meta (on the Go side)
Thanks @elithrar for your timely response, as for the details;
- Example Requests
This below fails
General:
Request URL: http://localhost:4000/State/Create
Request Method: POST
Status Code: 403 Forbidden
Remote Address: [::1]:4000
Referrer Policy: no-referrer-when-downgrade
Request Headers:
POST /State/Create HTTP/1.1
Host: localhost:4000
Connection: keep-alive
Content-Length: 29
Origin: http://localhost:4000
X-CSRF-Token: zCH8TUyAKC/YjFyO05s1dhreOlEDG3tciqmtOSiWmRYTjEVZ5wdYcVkSfFSr/V0IRZbH/7slt/9ikBUc0zQNFA==
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36
Content-Type: text/plain;charset=UTF-8
Accept: /
Referer: http://localhost:4000/InvitationLists/SystemAdmin
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: alert=MTU0NTUyNDM3NXxEdi1CQkFFQ180SUFBUkFCRUFBQUpQLUNBQUlHYzNSeWFXNW5EQWNBQld4bGRtVnNBQVp6ZEhKcGJtY01CUUFEYlhObkFBPT188QDjKJDRm36GtorlsakqdgbqFwrggW71VxDeRDrF_04=; _gorilla_csrf=MTU0NjE5NzA4MnxJbmsxYVhCSEwwWXJhazVUZUdKdGMxbFlXVlUwVkd0SkwxQkhTVVZvTjNoa1lVcEtSM1kyU3psWlpXczlJZ289fKPcYpwMTUj2jawLF13plYwX9VbNvfYsG69dlmYAI0RJ; session=MTU0NjE5NzExNXxEdi1CQkFFQ180SUFBUkFCRUFBQV83WF9nZ0FEQm5OMGNtbHVad3dHQUFSeWIyeGxCbk4wY21sdVp3d01BQXBUVlZCRlVrRkVUVWxPQm5OMGNtbHVad3dRQUE1eVpXMWxiV0psY2w5MGIydGxiZ1p6ZEhKcGJtY01MUUFyUlZsR1VsZDBNSGhRU2xKM2FsWnZMVE10UkROUWQxUTNhWHBKTkZob1pUbFdRa3hpU0V0VU9VZFRPQVp6ZEhKcGJtY01CZ0FFZFhObGNnWnpkSEpwYm1jTUpnQWtOVE13WlRNellqVXRNMkprTkMwMFpUVmlMV0pqWm1FdE9EaGpOVGcwTTJKaE1EQmt8qxI8qh04CsdzGjSmimmTjcWMZ0cGLz2EAOovWf76SlA=; arp_scroll_position=300
This below passes
General:
Request URL: http://localhost:4000/State/Create
Request Method: POST
Status Code: 200 OK
Remote Address: [::1]:4000
Referrer Policy: no-referrer-when-downgrade
Request Headers:
Accept: /
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Content-Length: 231
Content-Type: text/plain;charset=UTF-8
Cookie: alert=MTU0NTUyNDM3NXxEdi1CQkFFQ180SUFBUkFCRUFBQUpQLUNBQUlHYzNSeWFXNW5EQWNBQld4bGRtVnNBQVp6ZEhKcGJtY01CUUFEYlhObkFBPT188QDjKJDRm36GtorlsakqdgbqFwrggW71VxDeRDrF_04=; _gorilla_csrf=MTU0NjE5NzA4MnxJbmsxYVhCSEwwWXJhazVUZUdKdGMxbFlXVlUwVkd0SkwxQkhTVVZvTjNoa1lVcEtSM1kyU3psWlpXczlJZ289fKPcYpwMTUj2jawLF13plYwX9VbNvfYsG69dlmYAI0RJ; session=MTU0NjE5NzExNXxEdi1CQkFFQ180SUFBUkFCRUFBQV83WF9nZ0FEQm5OMGNtbHVad3dHQUFSeWIyeGxCbk4wY21sdVp3d01BQXBUVlZCRlVrRkVUVWxPQm5OMGNtbHVad3dRQUE1eVpXMWxiV0psY2w5MGIydGxiZ1p6ZEhKcGJtY01MUUFyUlZsR1VsZDBNSGhRU2xKM2FsWnZMVE10UkROUWQxUTNhWHBKTkZob1pUbFdRa3hpU0V0VU9VZFRPQVp6ZEhKcGJtY01CZ0FFZFhObGNnWnpkSEpwYm1jTUpnQWtOVE13WlRNellqVXRNMkprTkMwMFpUVmlMV0pqWm1FdE9EaGpOVGcwTTJKaE1EQmt8qxI8qh04CsdzGjSmimmTjcWMZ0cGLz2EAOovWf76SlA=; arp_scroll_position=200
Host: localhost:4000
Origin: http://localhost:4000
Referer: http://localhost:4000/States
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36
X-CSRF-Token: oEQz/WvevgfNNciIkf4zqcMJy3wsI1IT+id/Y23cI+hr3JrmmqAy03xbo5DMewvngTb3Hiik7k6StTncz2FCAQ==
- Whether Cross Origin request?
No, they are not
- Whether the cookie is being sent on all requests?
Yes
- How you are injecting the token into the page meta (on the Go side)
I have a function which contains a func Map (as below snippets) that I use in rendering all my html templates...
token := csrf.Token(r)
//fmt.Println("csrtT: ", token)
tpl := v.Template.Funcs(template.FuncMap{
"csrfToken": func() (string) {
return token
},
})
if err := tpl.ExecuteTemplate(&buf, v.Layout, vd); err != nil {
log.Println(err)
http.Error(w, "Something went wrong. If the problem persists, please email support@feerack.com", http.StatusInternalServerError)
return
}
io.Copy(w, &buf)
Then on my masterpage i use this meta tag in setting the csrfToken:
meta name="gorilla.csrf.Token" content="{{csrfToken}}"
@elithrar, I hope I answered all your questions as in above regarding the details, if not please let me know, thanks
this is the response header of the passed one:
Content-Length: 32
Content-Type: text/plain; charset=utf-8
Date: Sun, 30 Dec 2018 19:44:55 GMT
Vary: Cookie
this is the response header of the failed one:
Content-Length: 31
Content-Type: text/plain; charset=utf-8
Date: Sun, 30 Dec 2018 22:26:57 GMT
X-Content-Type-Options: nosniff
Please sir, I don't understand what you mean by the 'Set-Cookie header', where do I get it from, still from the Network tab of the browser?
Sorry @elithrar, for the delay in response, I have screen shot what I believe you mean by the Set-Cookie header, check attached image below, thanks
Not quite.
- Open the Web Inspector to the Network tab
- Reproduce the issue you have.
- Notice the failed requests, click on it on the left hand side, and choose the "Headers" tab.
- Paste (text, please) the Response headers from that failed request.
I would also consider:
- Is your XHR request always getting a non-nil token? (You don't check for that in your JS code)
- That your page always has a valid
<meta>
tag with the populated token - How you're setting up the middleware itself - where do you call
csrf.Protect(...)
- ?
Not quite.
- Open the Web Inspector to the Network tab
- Reproduce the issue you have.
- Notice the failed requests, click on it on the left hand side, and choose the "Headers" tab.
- Paste (text, please) the Response headers from that failed request.
Response Headers
Content-Length: 31
HTTP/1.1 403 Forbidden
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Date: Wed, 02 Jan 2019 15:37:15 GMT
Content-Length: 31
I would also consider:
- Is your XHR request always getting a non-nil token? (You don't check for that in your JS code)
- That your page always has a valid
<meta>
tag with the populated token- How you're setting up the middleware itself - where do you call
csrf.Protect(...)
- ?
- Yes, its always getting a non-nil token, I do log it out on the console to verify this
- Yes it does because the script logs out different tokens on each request fetched from the meta tag
- On my main.go file, the snippet of the middleware usage is below
isProd := false
b, err := rand.Bytes(32)
Must(err)
csrfMw := csrf.Protect(b, csrf.Secure(isProd))
Thanks @Kenmobility - still need more detail (please be verbose): show me how your router is set up, what router you're using, and how the CSRF middleware is applied.
I can also see that you're using a random key each time your application starts, so if you're restarting often in development you'll find that previously issued CSRF cookies are invalid.
I'm not seeing a Set-Cookie
header with the CSRF cookie in the response headers, either. I can see that in one example the user has a cookie, but not all.
What I'm having a hard time understanding: you posted a "passing" and "failing" request, but both requests appear to be identical. I don't have enough information, or any ability to reproduce your issue locally, to determine why that's happening.
package main
import (
"net/http"
"fmt"
"os"
"github.com/kenmobility/bios/controllers"
"github.com/kenmobility/bios/models"
"github.com/kenmobility/bios/rand"
"github.com/kenmobility/bios/middleware"
"github.com/gorilla/mux"
"github.com/gorilla/csrf"
)
func Must(err error) {
if err != nil {
panic(err)
}
}
func main() {
services, err := models.NewServices()
Must(err)
port := os.Getenv("PORT")
//fmt.Println("port: ", port)
if port == "" {
port = "4000"
}
r := mux.NewRouter()
userMw := middleware.User{Us: services.User}
requireUserMw := middleware.RequireUser{UserField: userMw}
isProd := false
b, err := rand.Bytes(32)
Must(err)
csrfMw := csrf.Protect(b, csrf.Secure(isProd))
r.PathPrefix("/public/").Handler(http.StripPrefix("/public/", http.FileServer(http.Dir("public/"))))
r.HandleFunc("/Dashboard", requireUserMw.Authorised(usersC.ViewDashboard)).Methods("GET")
r.HandleFunc("/State/Create", requireUserMw.SuperAdminAndSystemAdmin(locationC.CreateNewState)).Methods("POST")
r.HandleFunc("/SystemAdmin/Invite", requireUserMw.SuperAdmin(usersC.InitiateSystemAdminInvite)).Methods("POST")
r.HandleFunc("/Users/Invite/Delete", requireUserMw.SuperAdminAndSystemAdmin(usersC.DeleteInvite)).Methods("POST")
http.ListenAndServe(":"+port, userMw.ApplyUser(csrfMw(r)))
}
Thanks @Kenmobility - still need more detail (please be verbose): show me how your router is set up, what router you're using, and how the CSRF middleware is applied.
I can also see that you're using a random key each time your application starts, so if you're restarting often in development you'll find that previously issued CSRF cookies are invalid.
I'm not seeing a
Set-Cookie
header with the CSRF cookie in the response headers, either. I can see that in one example the user has a cookie, but not all.What I'm having a hard time understanding: you posted a "passing" and "failing" request, but both requests appear to be identical. I don't have enough information, or any ability to reproduce your issue locally, to determine why that's happening.
Thanks so much @elithrar, as for the random strings for the token, I have tried using a static string, still was still getting the error of invalid token.
As for the passing and failing; its still like a surprise and mystery to me because both javascript code are on same file with exact syntax, the only line that changes is the line of the url handler that handles each request, but one passes while others don't, am still wondering what could be the cause.
Thanks @Kenmobility - still need more detail (please be verbose): show me how your router is set up, what router you're using, and how the CSRF middleware is applied.
I can also see that you're using a random key each time your application starts, so if you're restarting often in development you'll find that previously issued CSRF cookies are invalid.
I'm not seeing a
Set-Cookie
header with the CSRF cookie in the response headers, either. I can see that in one example the user has a cookie, but not all.What I'm having a hard time understanding: you posted a "passing" and "failing" request, but both requests appear to be identical. I don't have enough information, or any ability to reproduce your issue locally, to determine why that's happening.
Sorry, I mistakenly closed this
This issue has been automatically marked as stale because it hasn't seen a recent update. It'll be automatically closed in a few days.