--- title: Password Signup & Login description: Wire email/password into HTTP handlers and sessions. --- import LLMExport from '../../components/LLMExport.astro'; This builds on [Email & Password](/email-password). You'll wire up HTTP handlers for signup and login that create sessions. ## Before you start - Go 1.22+ - Completed code from [Users & Sessions](/users-sessions) and [Email & Password](/email-password) - A database connection via `database/sql` - Session middleware enabled so authenticated routes can read the session - HTML pages for signup/login (server-rendered templates or your framework's view layer) ## File layout Build on the earlier file structure with an HTTP handlers file: ```diff /auth /sessions.go /cookies.go /middleware.go /passwords.go /users.go /main.go +/handlers + /auth.go // signup/login POST handlers + /pages.go // signup/login page rendering (optional) ``` If your app keeps handlers in `main.go`, that's fine too. Keep the logic the same. ## Signup handler Put this in `handlers/auth.go`: ```go package handlers import ( "database/sql" "errors" "net/http" auth "github.com/.../auth" ) func HandleSignup(db *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { email := r.FormValue("email") password := r.FormValue("password") user, err := auth.CreateUser(r.Context(), db, email, password) if err != nil { if errors.Is(err, auth.ErrEmailTaken) { http.Error(w, "Email already registered", http.StatusConflict) return } if errors.Is(err, auth.ErrInvalidEmail) || errors.Is(err, auth.ErrPasswordTooShort) || errors.Is(err, auth.ErrPasswordTooLong) || errors.Is(err, auth.ErrPasswordBreached) { http.Error(w, err.Error(), http.StatusBadRequest) return } http.Error(w, "Internal error", http.StatusInternalServerError) return } // Create session and log them in token, err := auth.CreateSession(r.Context(), db, user.ID) if err != nil { http.Error(w, "Internal error", http.StatusInternalServerError) return } auth.SetSessionCookie(w, token) http.Redirect(w, r, "/dashboard", http.StatusSeeOther) } } ``` After signup, create a session immediately so the user is logged in. ## Login handler Put this in `handlers/auth.go`: ```go func HandleLogin(db *sql.DB) http.HandlerFunc { dummyHash, err := auth.HashPassword("dummy-password") if err != nil { return func(w http.ResponseWriter, r *http.Request) { http.Error(w, "Internal error", http.StatusInternalServerError) } } return func(w http.ResponseWriter, r *http.Request) { email := r.FormValue("email") password := r.FormValue("password") user, err := auth.GetUserByEmail(r.Context(), db, email) if err != nil { http.Error(w, "Internal error", http.StatusInternalServerError) return } hashToCompare := dummyHash if user != nil { hashToCompare = user.PasswordHash } valid := auth.VerifyPassword(password, hashToCompare) if user == nil || !valid { http.Error(w, "Invalid email or password", http.StatusUnauthorized) return } token, err := auth.CreateSession(r.Context(), db, user.ID) if err != nil { http.Error(w, "Internal error", http.StatusInternalServerError) return } auth.SetSessionCookie(w, token) http.Redirect(w, r, "/dashboard", http.StatusSeeOther) } } ``` Return the same error message whether the email doesn't exist or the password is wrong. This prevents attackers from discovering which emails are registered. ## Route wiring Put this in `main.go`: ```go import ( "database/sql" "net/http" auth "github.com/.../auth" handlers "github.com/.../handlers" ) func main() { db, _ := sql.Open("sqlite3", "app.db") mux := http.NewServeMux() allowedOrigin := "https://example.com" requireSameOrigin := func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if !auth.VerifyRequestOrigin(r, allowedOrigin) { http.Error(w, "Forbidden", http.StatusForbidden) return } next.ServeHTTP(w, r) }) } // Render pages mux.HandleFunc("GET /signup", handlers.HandleSignupPage) mux.HandleFunc("GET /login", handlers.HandleLoginPage) // Process form submissions mux.Handle("POST /signup", requireSameOrigin(handlers.HandleSignup(db))) mux.Handle("POST /login", requireSameOrigin(handlers.HandleLogin(db))) handler := auth.SessionMiddleware(db)(mux) http.ListenAndServe(":8080", handler) } ``` This applies origin checks to signup and login POST routes. ## Rendering pages Put this in `handlers/pages.go`: ```go package handlers import "net/http" func HandleSignupPage(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(`

Sign up



Already have an account? Log in

`)) } func HandleLoginPage(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(`

Log in



Need an account? Sign up

`)) } ``` Use standard HTML form fields: ```html ``` Flow checklist: 1. Read form values (`email`, `password`) 2. Create or look up user 3. Verify password (always compare against a hash on login) 4. Create session 5. Set session cookie and redirect ## Next up Head to [Verification & Recovery](/verification-recovery) to add email verification and password reset.