Untuk proses authentication, kita akan menambahkan fungsi baru pada file cmd/web/handlers.go yaitu authenticate().
Kemudian modifikasi fungsi Login() pada file cmd/web/handlers.go, kita akan lakukan modifikasi yaitu:
- Menambahkan kode redirect jika validasi gagal dan credentials tidak valid.
- Memanggil fungsi authenticate().
Kemudian manambahkan fungsi Profile pada file cmd/web/handler.go, digunakan untuk handling ketika user berhasil login
Serta memperbaiki fungsi render() pada file cmd/web/handler.go dengan mengganti parameter data menjadi td dan menambahkan field pada struct TemplateData serta menambahkan kode untuk mengisi data error dan flash dari session.
package main
import (
"html/template"
"log"
"net/http"
"path"
"time"
"webapp/pkg/data"
)
var templatePath = "./templates/"
func (app *application) Home(w http.ResponseWriter, r *http.Request) {
var td = make(map[string]any)
if app.Session.Exists(r.Context(), "test") {
msg := app.Session.GetString(r.Context(), "test")
td["test"] = msg
} else {
app.Session.Put(r.Context(), "test", "Page visit at "+time.Now().UTC().String())
}
_ = app.render(w, r, "home.page.gohtml", &TemplateData{Data: td})
}
func (app *application) Profile(w http.ResponseWriter, r *http.Request) {
_ = app.render(w, r, "profile.page.gohtml", &TemplateData{})
}
type TemplateData struct {
IP string
Data map[string]any
Error string
Flash string
User data.User
}
func (app *application) render(w http.ResponseWriter, r *http.Request, t string, td *TemplateData) error {
//parse template
parsedTemplate, err := template.ParseFiles(path.Join(templatePath, t), path.Join(templatePath, "base.layout.gohtml"))
if err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return err
}
//gunakan middleware yang telah kita buat.
td.IP = app.ipFromContext(r.Context())
td.Error = app.Session.PopString(r.Context(), "error")
td.Flash = app.Session.PopString(r.Context(), "flash")
//execute template
err = parsedTemplate.Execute(w, td)
if err != nil {
return err
}
return nil
}
func (app *application) Login(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm()
if err != nil {
log.Println(err)
http.Error(w, "bad request", http.StatusBadRequest)
return
}
//validation goes here
form := NewForm(r.PostForm)
form.Required("email", "password")
if !form.Valid() {
//redirect to login page with error message.
app.Session.Put(r.Context(), "error", "Invalid login credentials")
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
email := r.Form.Get("email")
password := r.Form.Get("password")
user, err := app.DB.GetUserByEmail(email)
if err != nil {
//redirect to login page with error message.
app.Session.Put(r.Context(), "error", "Invalid login")
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
//authenticate the user
// if not authenticated, redirect with error
if !app.authenticate(r, user, password) {
app.Session.Put(r.Context(), "error", "Invalid login")
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
//prevent fixation attack
_ = app.Session.RenewToken(r.Context())
//redirect to other page
app.Session.Put(r.Context(), "flash", "Successfully loged in")
http.Redirect(w, r, "user/profile", http.StatusSeeOther)
}
func (app *application) authenticate(r *http.Request, user *data.User, password string) bool {
if valid, err := user.PasswordMatches(password); err != nil || !valid {
return false
}
app.Session.Put(r.Context(), "user", user)
return true
}
Selanjutnya kita lakukan modifikasi pada cmd/web/main.go, untuk registrasi data.user type di session.
package main
import (
"encoding/gob"
"flag"
"log"
"net/http"
"webapp/pkg/data"
"webapp/pkg/db"
"github.com/alexedwards/scs/v2"
)
type application struct {
DSN string
DB db.PostgresConn //ubah struc menggunakan pacakge db
Session *scs.SessionManager
}
func main() {
gob.Register(data.User{})
//setup app config
app := application{}
//database connection string
flag.StringVar(&app.DSN, "dsn", "host=localhost port=5432 user=postgres password=postgres dbname=users sslmode=disable timezone=UTC connect_timeout=5", "Postgres Connection")
flag.Parse()
//connection to database
conn, err := app.connectToDB()
if err != nil {
log.Fatal(err)
}
defer conn.Close()
app.DB = db.PostgresConn{DB: conn} //ubah menggunakan package db
//get session manager
app.Session = getSession()
//start server
log.Println("Starting server on port 8080...")
err = http.ListenAndServe(":8080", app.routes())
if err != nil {
log.Fatal(err)
}
}
Kemudian kita buat template baru yang akan menampilkan profile user bila berhasil login. Buat file templates/profile.page.gohtml
{{template "base" .}}
{{define "content"}}
<div class="container">
<div class="row">
<div class="col">
<h1 class="mt-3">User Profile</h1>
<hr>
</div>
</div>
</div>
{{end}}
Menambahkan element pada templates/base.layout.go untuk menampilkan informasi dari session.
{{define "base"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
<title>Home</title>
</head>
<body>
<div class="container">
<div class="row">
<div class="content">
{{with .Flash}}
<div class="mt-3 alert alert-success" role="alert">
{{.}}
</div>
{{end}}
{{with .Error}}
<div class="mt-3 alert alert-danger" role="alert">
{{.}}
</div>
{{end}}
</div>
</div>
</div>
{{block "content" .}}
{{end}}
</body>
</html>
{{end}}
Selanjutnya kita registrasikan route pada cmd/web/routes.go
package main
import (
"net/http"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)
func (app *application) routes() http.Handler {
mux := chi.NewRouter()
//register middleware
mux.Use(middleware.Recoverer)
mux.Use(app.addIPToContext)
mux.Use(app.Session.LoadAndSave) //middleware yang disediakan dari session manager
//register routes
mux.Get("/", app.Home)
mux.Post("/login", app.Login)
mux.Get("/user/profile", app.Profile)
//static assets
fileServer := http.FileServer(http.Dir("./static"))
mux.Handle("/static/*", http.StripPrefix("/static/", fileServer))
return mux
}
Jika kita jalankan aplikasi, lalu coba login dengan skenario
- user atau password kosong.
- user atau password salah.
- user dan password benar.
Maka pada web browser akan ditampilkan informasi error message atau login berhasil.

Sampai disini kita sudah selesai membuat proses authentication. Pada modul selanjutnya kita akan mulai membuat membuat test untuk proses authentication yang menggunakan method Post.