Repository navigation
Authentication and Security
How BookStorage handles user authentication, session management, authorization, and HTTP security hardening.
- Local authentication
- Session management
- Google OAuth
- Roles and permissions
- Account validation
- HTTP security hardening
New users register at /register with a username and password. The password is hashed using bcrypt (golang.org/x/crypto/bcrypt) before storage.
If BOOKSTORAGE_REQUIRE_ACCOUNT_VALIDATION is true (default), the account is created with validated = 0 and must be approved by an admin before the user can log in.
Users authenticate at /login with username and password. The submitted password is compared against the stored bcrypt hash.
The codebase also supports legacy password formats (Werkzeug pbkdf2 and plain text comparison) for migration purposes.
Passwords are stored as bcrypt hashes. There is no enforced minimum length at the application level beyond what bcrypt supports.
- On successful login, a 32-byte random token is generated (base64 URL-encoded)
- The SHA-256 hash of the token is stored in the
sessionstable (the raw token is never persisted) - A cookie named
sessionis set with the raw token value
| Property | Value |
|---|---|
| Name | session |
| HttpOnly | true |
| SameSite | Varies by environment |
| Secure |
true in production |
| Path | / |
| TTL | Duration | Behavior |
|---|---|---|
| Sliding TTL | 2 hours | Resets on every authenticated request |
| Absolute TTL | 24 hours | Maximum session lifetime from creation |
- If a user is inactive for more than 2 hours, the session expires
- Regardless of activity, sessions expire 24 hours after creation
- On each request,
last_seen_atis updated and the cookie TTL is refreshed
-
Logout: deletes the current session via
GET /logout -
Logout all: revokes all sessions for the user via
POST /profile/logout_all(setsrevoked_aton all sessions) -
Account deletion:
POST /profile/deleteremoves the account and all associated data
func hashSessionToken(token string) string {
h := sha256.Sum256([]byte(token))
return hex.EncodeToString(h[:])
}Only the SHA-256 hash is stored in the database. Even if the database is compromised, raw session tokens cannot be recovered.
BookStorage supports optional Sign in with Google via OAuth 2.0.
Set three environment variables (see Configuration):
-
BOOKSTORAGE_PUBLIC_ORIGIN(must behttpsin production) BOOKSTORAGE_GOOGLE_CLIENT_IDBOOKSTORAGE_GOOGLE_CLIENT_SECRET
sequenceDiagram
participant User
participant BookStorage
participant Google
User->>BookStorage: GET /auth/google
BookStorage->>BookStorage: Generate state + PKCE code_verifier
BookStorage->>BookStorage: Store in oauth_states table
BookStorage->>Google: Redirect to Google consent
Google->>User: Show consent screen
User->>Google: Approve
Google->>BookStorage: GET /auth/google/callback?code=...&state=...
BookStorage->>BookStorage: Validate state from oauth_states
BookStorage->>Google: Exchange code for tokens
Google->>BookStorage: ID token + access token
BookStorage->>BookStorage: Extract google_sub, email, name
BookStorage->>BookStorage: Find or create user by google_sub
BookStorage->>User: Set session cookie, redirect to dashboard
-
Link: authenticated users can link their Google account via
GET /auth/google/link -
Unlink: users with a local password can unlink via
POST /profile/google/unlink - Users without a local password cannot unlink (they would lose access)
OAuth states are stored in the oauth_states table with:
- A hashed state parameter
- A PKCE
code_verifier - An expiration timestamp
- The intended purpose (
loginorlink)
BookStorage has three permission levels:
| Role | Capabilities |
|---|---|
| User | Manage own library, profile, reading sites, export/import |
Admin (is_admin) |
All user capabilities + account management, monitoring, database tools, catalog enrichment, app updates |
Super-admin (is_superadmin) |
All admin capabilities + SQLite-to-PostgreSQL migration |
The initial superadmin is created automatically on first startup if no superadmin exists, using:
-
BOOKSTORAGE_SUPERADMIN_USERNAME(default:superadmin) BOOKSTORAGE_SUPERADMIN_PASSWORD
Admins can promote other users to admin via GET /admin/promote/{id}.
| Middleware | Check |
|---|---|
RequireLogin |
Valid session exists |
RequireAdmin |
is_admin = 1 |
RequireSuperadmin |
is_admin = 1 AND is_superadmin = 1
|
RequireWebOnly |
Request is not from mobile PWA |
For /api/* routes, RequireLogin returns a JSON error:
{"error": "session_expired"}For HTML routes, it redirects to /login.
When BOOKSTORAGE_REQUIRE_ACCOUNT_VALIDATION=true (default):
- New accounts are created with
validated = 0 - Users cannot log in until an admin approves their account
- Admins approve accounts via Admin > Accounts (
GET /admin/approve/{id})
When set to false, users can log in immediately after registration.
Authenticated mutating requests (POST, PATCH, DELETE, PUT) are validated against the Origin or Referer header to prevent CSRF attacks. Requests with mismatched origins are rejected with 403 Forbidden.
Lightweight rate limiting is applied on sensitive endpoints:
- Authentication routes (login, registration)
- Write-heavy API routes
The SecurityHeaders middleware sets protective response headers including:
- Content Security Policy (CSP)
- X-Frame-Options
- X-Content-Type-Options
- Referrer-Policy
When BOOKSTORAGE_ENABLE_HSTS=true and the app is served over HTTPS, the Strict-Transport-Security header is sent.
| Timeout | Default | Purpose |
|---|---|---|
ReadHeaderTimeout |
5s | Time to read request headers |
ReadTimeout |
15s | Time to read the full request (BOOKSTORAGE_HTTP_READ_TIMEOUT_SEC) |
WriteTimeout |
120s | Time to write the response (BOOKSTORAGE_HTTP_WRITE_TIMEOUT_SEC) |
IdleTimeout |
60s | Keep-alive idle timeout |
These timeouts protect against slowloris-style attacks and resource exhaustion.
CI / CD — Next: learn about the CI/CD pipeline and deployment tools.