---
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.