Authentication
The accounts app signs users in with server-side sessions and CSRF checks. Every generated project includes it.
#How it fits together
Cicadel has one core accounts service and two HTTP adapters:
- Browser adapter. Server-rendered HTML, server-side SCS sessions and CSRF checks.
- JSON bearer adapter. For API clients that send
Authorization: Bearer ….
Both adapters read the same PostgreSQL users, account sessions, recovery tokens, groups and permissions. Disabling an account, resetting its password or revoking its sessions affects both. A browser never receives the account bearer token in HTML or JSON.
#Ask your agent
Read https://cicadel.fauzaanu.com/en/0.2.8/topics/authentication/index.md.
Add browser sign-in to my application using the accounts starter or adapter.
Use PostgreSQL sessions, CSRF protection and verified recovery addresses.
Show the registration and route protection code. Test sign-in, sign-out,
and rejection of revoked sessions; keep secrets outside source code.#Create the starter
From the framework checkout:
go build -o bin/cicadel ./cmd/cicadel
./bin/cicadel startproject /tmp/myapp example.com/myapp
cd /tmp/myapp
go mod edit -replace=github.com/fauzaanu/cicadel=/absolute/path/to/Cicadel
go mod edit -replace=github.com/fauzaanu/cicadel/contrib/accounts=/absolute/path/to/Cicadel/contrib/accounts
go mod edit -replace=github.com/fauzaanu/cicadel/contrib/formsets=/absolute/path/to/Cicadel/contrib/formsets
go mod edit -replace=github.com/fauzaanu/cicadel/contrib/migrations=/absolute/path/to/Cicadel/contrib/migrations
go mod edit -replace=github.com/fauzaanu/cicadel/contrib/querygen=/absolute/path/to/Cicadel/contrib/querygen
go mod tidy
export DATABASE_URL='postgres://USER:PASSWORD@localhost/myapp?sslmode=disable'
export CICADEL_DEBUG=true
export CICADEL_SECURE_COOKIES=false
go run manage.go migrate
CICADEL_USER_PASSWORD='a local account password' go run manage.go createuser owner@example.com
go run manage.go runserverstartproject always includes accounts, formsets and the standard migration
workflow. Runtime commands need PostgreSQL.
The accounts module ships with the framework at the same version. Keep all core modules on one version, and commit your resolved Go dependencies.
#Who can have an account
- The stored username is the account’s verified recovery address. Create accounts only for email addresses whose ownership you have verified.
- Requests cannot supply a separate delivery address.
- There is no public registration endpoint and no administrator panel.
- Use the service’s
CreateAPI for your own provisioning and invitation workflows.
#Screens and actions
The default prefix is /accounts. Change it with accounts.Pages.Prefix.
| Route | GET | POST |
|---|---|---|
/accounts | Signed-in account and session actions | — |
/accounts/login | Login form | Rotate session and sign in |
/accounts/recover | Recovery form | Deliver a reset link to the verified destination |
/accounts/reset | Reset form with token | Consume the token once and replace the password |
/accounts/change-password | Current/new password form | Replace password and revoke all sessions |
/accounts/logout | — | Revoke current account session and destroy its cookie session |
/accounts/revoke | — | Revoke all account sessions and sign out |
/static/accounts/app.<hash>.css | Embedded compiled stylesheet, published through App.Static | — |
To customize the screens, compose your own pages with contrib/accounts/accountui.
#Integrate an existing application
Create the service and the browser adapter in your project setup. Then register the returned app:
service, err := accounts.New(accounts.Options{
DB: db,
DeliverRecovery: DeliverRecovery,
})
if err != nil {
return err
}
browser, err := service.Browser(security.NewSessions(store, true))
if err != nil {
return err
}
authApp, err := browser.HTMLApp(accounts.Pages{
Brand: "My application",
Logger: logger,
})
if err != nil {
return err
}
installed = append(installed, authApp)This fragment uses your PostgreSQL db, shared session store, logger, recovery
callback and existing []cicadel.App. Pass installed to the project’s
cicadel.New call.
- Register the adapter once.
- Run
migratebefore serving.
The generated starter has the full setup in project/application.go.
#Protect a view
Send anonymous visitors to the sign-in page, like Django’s login_required.
Install browser.Middleware and browser.LoginRequired on the app that owns the
views:
notes := cicadel.App{
Name: "notes",
Middleware: []cicadel.Middleware{
browser.Middleware,
browser.LoginRequired("/accounts/login"),
},
Routes: []urls.Route{
urls.Path("GET", "/notes/{id}", "notes:detail", detail),
},
}An anonymous GET /notes/42 is redirected to
/accounts/login?next=%2Fnotes%2F42. The sign-in page carries next in its form
and returns there after a successful sign-in.
- Unsafe requests (POST, DELETE …) are redirected without
next, so signing in never repeats an action. - htmx requests get
HX-Redirect, so the whole page navigates.nextis the page the request came from (HX-Current-URL) when it is on the same host. nextis only ever a local path. The sign-in page ignores//evil.test,https://…, backslashes and control characters, and returns to the account page instead.LoginRequiredpanics at startup when its login URL is not a local path.
For JSON routes, use browser.RequireLogin instead. It answers 401.
- Apply
browser.Middlewarefirst. It loads the session, checks CSRF and resolves the user. - Read the user with
accounts.UserFromContextinside handlers. - Both guards check a freshly resolved account, not only a stored SCS user ID.
Warning
Login is not authorization. Object permissions still need explicit application policy and scoped database queries. Check them inside the handler.
HTMLApp already applies LoadAndSave → CSRF → account resolution to its own routes.
Do not wrap those routes with the same middleware again.
#Sessions
- The starter stores sessions in the PostgreSQL table
accounts_browser_sessions, using SCS’s configurable table name. You can supply a custom store instead. - Migration 002 adds that table without rewriting existing accounts history.
- Sessions are HttpOnly, SameSite=Lax and secure by default.
- Sessions rotate on login and clear at logout.
- Account expiry and revocation are checked on every browser request.
- SCS expiry can end a session before its account token expires.
accounts:cleanupremoves expired tokens. - Every unsafe method requires CSRF.
- Bodies under
browser.Middlewarekeep the application’s limit,settings.MaxBodyBytes, or a route’s own limit (see Limit request bodies). The account pages bound their own forms to 8 KiB. - htmx requests send the CSRF token as a header when the page sets
layout.Props.CSRFToken(see htmx).
#API clients
For API-only clients, use service.HTTPApp(prefix) and service.Middleware with
an explicit Authorization: Bearer … header.
Register either adapter once, under the accounts app name. If a host needs both,
combine their routes into that one app. Scope the cookie middleware to the HTML
routes.
Warning
Never apply cookie CSRF middleware to a bearer-only API by accident.
#React to account changes
The accounts service emits typed events such as accounts.UserCreated,
accounts.LoggedIn and accounts.PasswordChanged. See
accounts events.
#Recovery delivery
project.DeliverRecovery is the application-owned callback that sends reset links.
It:
- sends to the stored verified address;
- builds a local reset route from the configured origin;
- passes the token only to the mail transport.
Recovery reveals nothing about which usernames exist:
- Known and unknown users get the same recovery response.
- Missing delivery configuration is handled without revealing whether a username exists.
- Delivery errors are logged without tokens, addresses or server response text.
Passwords must be 12–72 bytes. Applications may add their own password policy.
Configure these starter environment variables:
| Variable | Purpose |
|---|---|
CICADEL_PUBLIC_URL | HTTPS origin used to construct reset links |
CICADEL_MAIL_FROM | Sender mailbox |
CICADEL_SMTP_ADDRESS | SMTP host:port with STARTTLS |
CICADEL_SMTP_USERNAME, CICADEL_SMTP_PASSWORD | Optional SMTP credentials |
CICADEL_LOG_FORMAT, CICADEL_LOG_LEVEL | JSON/text format and log threshold |
#Deploy
- Use HTTPS and secure cookies.
- Configure allowed hosts.
- Apply gateway rate limits to the login and recovery endpoints.
- Schedule
manage.go accounts:cleanup. - Do not log query strings on reset routes at the proxy.
Reset pages send no-referrer, no-store and a restrictive content security
policy. See email, logging and
operations for configuration details.