gogo-chi

Golang Post 400 bad request


I am trying to do a simple login. I followed a tutorial on Udemy. It went through rendering templates with a render function. I decided I wanted to handle templates differently and not have a render function and changed everything around. But I did not touch the PostLoginHandler. Since I made this change my post for "/login" has stopped working. I get "400 bad request" and I can't figure out why. I'll include the code that I think is important. I will say that I think it has something to do with the request, and/or CRSF token the tutorial added that I have not used since the changes. I am not using template.Must(template.ParseGlob("./templates/*.tmpl")) for handling templates.

Also, I'm sorry about the long post, I wasn't sure what info would be needed. Thank you in advance for any responses.

Old render function.

func RenderTemplate(w http.ResponseWriter, r *http.Request, t string, pd *models.PageData) {
    var tmpl *template.Template
    var err error
    _, inMap := tmplCache[t]
    if !inMap {
        err = makeTemplateCache(t)
        if err != nil {
            fmt.Println(err)
        } else {
            fmt.Println("Template in cache")
        }
    }
    tmpl = tmplCache[t]

    pd = AddCSRFData(pd, r)

    err = tmpl.Execute(w, pd)
    if err != nil {
        fmt.Println(err)
    }
}

routes

mux := chi.NewRouter()
mux.Use(middleware.Recoverer)
mux.Use(NoSurf)
mux.Use(SetupSession)

mux.Post("/login", handlers.Repo.PostLoginHandler)

It's not even getting to the PostLoginHandler, but the code is

func (m *Repository) PostLoginHandler(w http.ResponseWriter, r *http.Request) {
    log.Println("here")

    //strMap := make(map[string]string)
    _ = m.App.Session.RenewToken(r.Context())
    err := r.ParseForm()
    if err != nil {
        log.Fatal(err)
    }
    email := r.Form.Get("email")
    password := r.Form.Get("password")

    form := forms.New(r.PostForm)
    form.HasRequired("email", "password")
    form.IsEmail("email")

    if !form.Valid() {
        err := m.App.UITemplates.ExecuteTemplate(w, "login.page.tmpl", &models.PageData{Form: form})
        if err != nil {
            return
        }
        //render.RenderTemplate(w, r, "login.page.tmpl", &models.PageData{Form: form})
        return
    }
    id, _, err := m.DB.AuthenticateUser(email, password)
    if err != nil {
        m.App.Session.Put(r.Context(), "error", "Invalid Email OR Password")
        http.Redirect(w, r, "/login", http.StatusSeeOther)
        return
    }
    m.App.Session.Put(r.Context(), "user_id", id)
    m.App.Session.Put(r.Context(), "flash", "Valid Login")
    http.Redirect(w, r, "/", http.StatusSeeOther)
    //render.RenderTemplate(w, r, "page.page.tmpl", &models.PageData{StrMap: strMap})
}

and lastly, the HTML is a simple form

<form method="post" action="/login">

   {{/*<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">*/}}

   <h1 class="h3 mb-3 fw-normal">Please sign in</h1>
   <div class="form-floating">
       <input type="email" class="form-control" id="email" name="email" placeholder="name@example.com">
       <label for="email">Email address</label>
   </div>
   <div class="form-floating">
       <input type="password" class="form-control" id="password" name="password" placeholder="Password">
       <label for="password">Password</label>
   </div>
   <div class="checkbox mb-3">
       <label>
           <input type="checkbox" value="remember-me"> Remember me
       </label>
   </div>
   <button class="w-100 btn btn-lg btn-primary" type="submit">Sign in</button>
</form>

Solution

  • 400 bad request is getting with Nosurf middleware.

    Firstly, you should un-comment this in your template. As this is required for crsf validation.

    <input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
    

    Secondly, you should ensure that the CSRFToken correctly passed into the template. I hope this is updating within AddCSRFData function.

    I get "400 bad request" and I can't figure out why.

    By default the package is not logging anything when an error happened. But it is possible to override the failure handler to see what exact reason caused the 400 bad request.

    See the sample code

    package main
    
    import (
        "fmt"
        "html/template"
        "net/http"
    
        "github.com/go-chi/chi"
        "github.com/go-chi/chi/middleware"
        "github.com/justinas/nosurf"
    )
    
    var formTemplate = `
        <html>
        <body>
    
            <form method="post" action="/submit">
                <!--  comment this and see error -->
                <input type="hidden" name="csrf_token" value="{{ .CSRFToken }}"/>
    
                <h1 class="h3 mb-3 fw-normal">Please sign in</h1>
                <div class="form-floating">
                    <input type="email" class="form-control" id="email" name="email" placeholder="name@example.com">
                    <label for="email">Email address</label>
                </div>
                <div class="form-floating">
                    <input type="password" class="form-control" id="password" name="password" placeholder="Password">
                    <label for="password">Password</label>
                </div>
                <div class="checkbox mb-3">
                    <label>
                        <input type="checkbox" value="remember-me"> Remember me
                    </label>
                </div>
    
                <button class="w-100 btn btn-lg btn-primary" type="submit">Sign in</button>
            </form>
        </body>
        </html>
    `
    var tmpl = template.Must(template.New("t1").Parse(formTemplate))
    
    // FailureFunction
    // Overriding the default nosurf failure Handler
    func FailureFunction() http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            fmt.Printf("Request Failed. Reason: %v", nosurf.Reason(r))
            http.Error(w, http.StatusText(nosurf.FailureCode), nosurf.FailureCode)
        })
    }
    
    // NoSurf
    // Setting up the handler with overrriding default nosurf failure Handler
    func NoSurf(handler http.Handler) http.Handler {
        obj := nosurf.New(handler)
        obj.SetFailureHandler(FailureFunction()) // Override default failure Handler
        return obj
    }
    
    func main() {
        r := chi.NewRouter()
    
        // Add middleware
        r.Use(middleware.Logger)
        r.Use(middleware.Recoverer)
        r.Use(NoSurf)
    
        r.Get("/", HomeHandler)
        r.Post("/submit", SubmitHandler)
    
        http.ListenAndServe(":8080", r)
    }
    
    func HomeHandler(w http.ResponseWriter, r *http.Request) {
        token := nosurf.Token(r) // generating the token
    
        data := map[string]interface{}{
            "CSRFToken": token, // comment this and see the error
        }
        err := tmpl.Execute(w, data)
        if err != nil {
            http.Error(w, "unable to execute the template", http.StatusInternalServerError)
            return
        }
    }
    
    func SubmitHandler(w http.ResponseWriter, r *http.Request) {
        err := r.ParseForm()
        if err != nil {
            http.Error(w, "Bad Request", http.StatusBadRequest)
            return
        }
    
        if !nosurf.VerifyToken(nosurf.Token(r), r.PostForm.Get("csrf_token")) {
            http.Error(w, "Invalid CSRF Token", http.StatusForbidden)
            return
        }
    
        w.Write([]byte("success"))
    }
    
    

    Hoping this will help you to resolve your problem.