# Browser authentication and the accounts starter

Cicadel has one core accounts service and two explicit HTTP adapters. Browser
applications use server-rendered HTML, server-side SCS sessions and CSRF checks.
API clients can use the JSON bearer adapter. Both adapters consult the same
PostgreSQL users, account sessions, recovery tokens, groups and permissions.
Disabling an account, resetting its password or revoking its sessions affects
both adapters. A browser never receives the account bearer token in HTML or JSON.

## Ask your agent

```text
Read https://cicadel.fauzaanu.com/en/dev/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:

```sh
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 runserver
```

Use an email address whose ownership you have verified before provisioning the
account. This starter treats the stored username as that verified recovery
address. Requests cannot supply a separate delivery address. There is no public
registration endpoint or administrator panel. The service's `Create` API remains
available for application-owned provisioning and invitation workflows.

The accounts module has no published release yet. The commands above use local checkout paths; commit your resolved Go dependencies and replace machine-specific paths before distributing the application. `startproject` always includes accounts, formsets and the standard migration workflow; runtime commands require PostgreSQL.

## Screens and actions

The default prefix is `/accounts`; `accounts.Pages.Prefix` can change it.

| 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 |
| `/accounts/style.css` | Embedded compiled stylesheet | — |

To customize the screens, compose your own pages with `contrib/accounts/accountui`.

## Integrate an existing application

In your project setup, create the service and browser adapter, then register the returned app:

```go
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 setup 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 and run `migrate` before serving. The generated accounts starter includes the full setup in `project/application.go`.

Protect another app's handler with the browser adapter:

```go
protected := browser.Middleware(browser.RequireLogin(http.HandlerFunc(dashboard)))
route := urls.Path("GET", "/dashboard", "dashboard:index", protected.ServeHTTP)
```

Add `route` to that app's routes. Import `net/http` and `github.com/fauzaanu/cicadel/urls`; `dashboard` is your handler. Check object-level permissions inside it.

`HTMLApp` owns LoadAndSave → CSRF → account-resolution middleware for its routes.
Do not wrap those routes with those same middleware again. For another app's
protected routes, apply `browser.Middleware` then `browser.RequireLogin`, and
use `accounts.UserFromContext` inside handlers. `RequireLogin` checks a freshly
resolved account, rather than only a stored SCS user ID. Object permissions still
require explicit application policy and scoped database queries.

The starter uses PostgreSQL `accounts_browser_sessions` through SCS's configurable
table name. Migration 002 adds the table without rewriting the existing accounts
history. A custom store may also be supplied. Sessions are HttpOnly, SameSite=Lax,
secure by default, rotated on login and cleared at logout. Account expiry and
revocation are checked on every browser request. SCS expiry can end a session
before the associated account token expires; `accounts:cleanup` removes expired
tokens. Browser forms are bounded to 8 KiB and all unsafe methods require CSRF.

For API-only clients use `service.HTTPApp(prefix)` and `service.Middleware` with
explicit `Authorization: Bearer …`. Register either adapter once under the
`accounts` app name. If a host needs both, combine their routes into that one app
and scope cookie middleware to the HTML routes; never apply cookie CSRF middleware
to a bearer-only API by accident.

## Recovery delivery and deployment

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 |

`project.DeliverRecovery` is the application-owned callback. It sends to the
stored verified address, constructs a local reset route using the configured
origin and passes the token only to the mail transport. Known and unknown users
receive the same recovery response. Delivery errors are logged without tokens,
addresses or server response text. Missing delivery configuration is handled
without revealing whether a username exists. Passwords must contain 12–72 bytes;
applications may add their own password policy.

Use HTTPS and secure cookies, configure allowed hosts, apply gateway rate limits
to login/recovery endpoints and schedule `manage.go accounts:cleanup`. Never log
query strings on reset routes at the proxy. Reset pages send `no-referrer`,
`no-store` and a restrictive content security policy. See [email](https://cicadel.fauzaanu.com/en/dev/topics/email/index.md),
[logging](https://cicadel.fauzaanu.com/en/dev/topics/logging/index.md) and [operations](https://cicadel.fauzaanu.com/en/dev/howto/operations/index.md) for configuration details.
