Part 5: Authentication¶
In this part you'll add the auth contrib app so that only logged-in users can vote.
Source code: tutorial/step05/
Add the Auth App¶
The auth contrib app provides WebAuthn (passkey) authentication with registration, login, and logout flows. It ships with default templates for all auth pages.
Update main.go:
import (
"github.com/oliverandrich/burrow/contrib/auth"
"github.com/oliverandrich/burrow/contrib/healthcheck"
)
srv := burrow.NewServer(
session.New(),
csrf.New(),
staticApp,
healthcheck.New(),
messages.New(),
bootstrap.New(),
pages.New(),
auth.New(), // new
polls.New(),
)
The auth app:
- Provides routes at
/auth/login,/auth/register,/auth/logout - Runs its own database migrations for user and credential tables
- Injects the current user into request context via middleware
Protect the Vote Route¶
Use auth.RequireAuth() middleware to restrict voting to authenticated users:
func (a *App) Routes(r chi.Router) {
r.Route("/polls", func(r chi.Router) {
r.Get("/", burrow.Handle(a.handlers.List))
r.Get("/{id}", burrow.Handle(a.handlers.Detail))
r.Get("/{id}/results", burrow.Handle(a.handlers.Results))
// Voting requires authentication.
r.Group(func(r chi.Router) {
r.Use(auth.RequireAuth())
r.Post("/{id}/vote", burrow.Handle(a.handlers.Vote))
})
})
}
chi.Router.Group() creates a sub-router with its own middleware stack. Only the vote route requires login — browsing questions and viewing results remain public.
If an unauthenticated user tries to vote, they'll be redirected to /auth/login. After logging in, they'll return to the page they came from.
Declare the Dependency¶
Since the polls app now depends on the auth app, declare it:
Burrow automatically sorts apps by dependencies during NewServer(), so you don't need to worry about registration order.
Show the User in the Navbar¶
Update the layout to display the current user's email and a sign-out button:
layoutData := map[string]any{
"Content": content,
"NavItems": burrow.NavItems(r.Context()),
"Messages": messages.Get(r.Context()),
"User": auth.UserFromContext(r.Context()), // new
}
In the layout template:
<ul class="navbar-nav">
{{ if .User -}}
<li class="nav-item">
<span class="nav-link text-body-secondary">{{ .User.Email }}</span>
</li>
<li class="nav-item">
<form method="post" action="/auth/logout">
<input type="hidden" name="gorilla.csrf.Token" value="{{ csrfToken }}">
<button type="submit" class="btn btn-link nav-link">Sign out</button>
</form>
</li>
{{ else -}}
<li class="nav-item">
<a class="nav-link" href="/auth/login">Sign in</a>
</li>
{{ end -}}
</ul>
Run It¶
Visit /auth/register to create an account (you'll need a browser that supports passkeys/WebAuthn). After registering, try voting — it should work. Sign out and try again — you'll be redirected to the login page.
What You've Learnt¶
auth.New()— configures the auth app with built-in default renderer and layoutauth.RequireAuth()— middleware that redirects unauthenticated users to loginauth.UserFromContext()— retrieves the authenticated user from request contextHasDependencies— declares inter-app dependencies for automatic ordering
Next¶
In Part 6, you'll add an admin panel to manage questions without touching the database directly.