1

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>
  • It looks the issue is related with `NoSurf` middleware. How you setting up this middleware in your code? Which package used for this? – PRATHEESH PC Jun 09 '23 at 06:22

1 Answers1

1

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.

PRATHEESH PC
  • 1,461
  • 1
  • 3
  • 15
  • Wow, thank you. This worked. This helped a lot. I was able to figure out how to incorporate that "AddCSRFData" function back in... I guess i didn't realize how important that was to the code. – Chris Williamson Jun 09 '23 at 12:05