I am using Justinas NoSurf for CSRF http package in my Go code. When I try to sign up and submit the post on my form, I get a "bad request" error. The problem occurs every time I'm inside a form and I try the Post method. When I press submit to signup then I get the "bad request" error. I know it has to do with the nosurf csrf package because that's when I started seeing the error.
This is my routes:
func (app *application) routes() http.Handler {
router := httprouter.New()
fileServer := http.FileServer(http.Dir("./ui/static/"))
router.Handler(http.MethodGet, "/static/*filepath", http.StripPrefix("/static", fileServer))
dynamicMiddleware := alice.New(app.sessionManager.LoadAndSave, noSurf)
router.Handler(http.MethodGet, "/signup", dynamicMiddleware.ThenFunc(app.signup))
router.Handler(http.MethodPost, "/signup", dynamicMiddleware.ThenFunc(app.signupSubmit))
//tidy up the middle wear
standardMiddleware := alice.New(app.RecoverPanicMiddleware, app.logRequestMiddleware,
securityHeadersMiddleware)
return standardMiddleware.Then(router)
}
Inside my data.go, I have this:
type templateData struct {
User []*models.User
ErrorsFromForm map[string]string
FormData url.Values
Flash string //flash is the key
CSRFToken string
IsAuthenticated bool
}
My handlers function look like this:
func (app *application) signup(w http.ResponseWriter, r *http.Request) {
// remove the entry from the session manager
flash := app.sessionManager.PopString(r.Context(), "flash")
data := &templateData{ //putting flash into template data
Flash: flash,
CSRFToken: nosurf.Token(r),
}
RenderTemplate(w, "signup.page.tmpl", data)
csrfToken := nosurf.Token(r)
fmt.Println("CSRF Token (GET):", csrfToken)
}
func (app *application) signupSubmit(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
name := r.PostForm.Get("name")
email := r.PostForm.Get("email")
password := r.PostForm.Get("password")
confirmpassword := r.PostForm.Get("confirmpassword")
fmt.Println("Form Data:", r.PostForm) // Print the entire form data
csrfToken := r.PostForm.Get("csrf_token")
fmt.Println("CSRF Token (POST):", csrfToken)
errors_user := make(map[string]string)
if strings.TrimSpace(name) == "" {
errors_user["name"] = "Name is required"
}
if strings.TrimSpace(email) == "" {
errors_user["email"] = "Email is required"
}
emailRegex := regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
if !emailRegex.MatchString(email) {
errors_user["email"] = "Invalid email"
}
if strings.TrimSpace(password) == "" {
errors_user["password"] = "Password is required"
} else if utf8.RuneCountInString(password) < 5 {
errors_user["password"] = "This field is too short (minimum is 5 characters)"
}
if password != confirmpassword {
errors_user["confirmpassword"] = "Password does not match"
}
if len(errors_user) > 0 {
data := &templateData{
ErrorsFromForm: errors_user,
CSRFToken: nosurf.Token(r),
}
RenderTemplate(w, "signup.page.tmpl", data)
return
}
err := app.user.Insert(name, email, password, confirmpassword)
if err != nil {
if errors.Is(err, models.ErrDuplicateEmail) {
app.sessionManager.Put(r.Context(), "flash", "Email already registered")
http.Redirect(w, r, "/signup", http.StatusSeeOther)
return
}
app.sessionManager.Put(r.Context(), "flash", "Email already registered")
http.Redirect(w, r, "/signup", http.StatusSeeOther)
return
}
app.sessionManager.Put(r.Context(), "flash", "Signup was successful")
http.Redirect(w, r, "/login", http.StatusSeeOther)
}
My signup.tmpl looks like this:
<form action="/signup" method="POST" class="signup-form">
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}"/>
{{with .Flash}}
<div class = "flash"> {{.}}</div>
{{end}}
{{ with .ErrorsFromForm.name }}
<label class="error">{{ . }}</label>
{{end}}
<div class="input-field">
<input type="text" name="name" class="input" placeholder="Username" value="{{ .FormData.Get "name" }}">
</div>
{{ with .ErrorsFromForm.email }}
<label class="error">{{ . }}</label>
{{end}}
<div class="input-field">
<input type="text" name="email" class="input" placeholder="Email" value="{{ .FormData.Get "email" }}">
</div>
{{ with .ErrorsFromForm.password }}
<label class="error">{{ . }}</label>
{{end}}
<div class="input-field">
<input type="password" name="password" class="input" placeholder="Password" value="{{ .FormData.Get "password" }}">
</div>
{{ with .ErrorsFromForm.confirmpassword }}
<label class="error">{{ . }}</label>
{{end}}
<div class="input-field">
<input type="password" name="confirmpassword" class="input" placeholder="Confirm Password" value="{{ .FormData.Get "confirmpassword" }}">
</div>
<input type="submit" class="submit" value="Signup"> </input>
<label><a href="/login">Already have an account?<span> Login</span></a></label>
</form>
I know the error is because of the nosurf csrf. If I try to remove it from all the files then the post would work.
I'm not sure how to check if the CSRFToken correctly passed into the template.