๐ฅ Users, Authentication, and Brokers
This section explains the authentication model, user roles, and how data is segregated between users and brokers.
๐ Authentication Model (Stateless JWT Cookie)
LibreFolio authenticates with a signed JWT kept in an HTTP-only cookie. The server stores no session state, neither in memory nor in the database.
๐ How it Works
- Login: The user sends their
username(or e-mail) andpasswordto the/api/v1/auth/loginendpoint. - Token Creation:
- The server verifies the credentials (using
bcrypthashing). - If valid, it signs a JWT with
JWT_SECRET(HS256) carrying the user ID (sub), the issue time (iat) and the expiry (exp):create_jwt_token()inbackend/app/services/auth_service.py.
- The server verifies the credentials (using
- Cookie Issuance: The server responds with a
Set-Cookieheader for thesessioncookie, which holds the JWT; itsmax_agematches the token's lifetime.HttpOnly: The cookie cannot be accessed by JavaScript (prevents XSS token theft).SameSite=Lax: Provides protection against CSRF attacks.Secure: decided bysession_cookie_secure()inbackend/app/api/v1/auth.pyfromSESSION_COOKIE_SECURE.auto, the default, sets it on HTTPS, seen directly or as the firstX-Forwarded-Protovalue of a reverse proxy;alwaysandneverforce it. Login, logout and account deletion use the same rule. See Configuration.
- Authenticated Requests: The browser automatically includes the session cookie in subsequent requests.
- Validation: On every request,
get_current_user()verifies the token's signature and expiry, then loads the user from the database: a missing or deactivated user gets401even with a valid token.
๐พ Session State, Restarts & TTL
- ๐ง Stateless: Nothing is stored server-side, so any uvicorn worker can validate any token, provided all workers share the same
JWT_SECRET. - ๐ Restarts: Sessions survive a restart only if
JWT_SECRETis set to a fixed value. Without it, every start generates a new random key, existing tokens no longer verify, and everyone is signed out. See JWT Secret for how the key is resolved. - โฑ๏ธ TTL (Time To Live): The session duration is configurable via the
session_ttl_hoursGlobal Setting (default: 24 hours), read at login. There is no refresh: after expiry the user logs in again. - ๐ช Revocation: Logout only deletes the cookie, and a password change leaves existing tokens valid until they expire. Deactivating or deleting the user cuts access at the next request; changing
JWT_SECRETsigns everyone out.
๐ค User Roles
There are two system-level user roles in LibreFolio:
-
๐ค Normal User:
- ๐ Can manage their own profile and settings.
- ๐ฆ Can create and manage their own brokers.
- ๐ค Can be granted access to other users' brokers.
-
๐ Superuser (Admin):
- โ Has all permissions of a normal user.
- โ๏ธ Can manage system-wide Global Settings.
- ๐ง Can manage other users (reset passwords, deactivate accounts) via CLI or API.
- ๐ Can access any broker and transaction in the system for support purposes.
๐๏ธ User-Broker Mapping and Data Segregation
Data in LibreFolio is segregated based on a clear ownership hierarchy, but allows for flexible sharing via the Broker Access Control (RBAC) system.
graph TD
UserA["User A (Owner)"] --> BrokerA1["Broker 1 (Degiro)"]
UserA --> BrokerA2["Broker 2 (IBKR)"]
UserB["User B (Viewer)"] -.-> BrokerA1
BrokerA1 --> TxA1["Transaction X"]
BrokerA2 --> TxA2["Transaction Y"]
Asset["Global Asset (e.g., Apple Inc.)"]
TxA1 -- "involves" --> Asset
- ๐ Ownership: A Broker is created by a User (the Owner).
- ๐ค Sharing: The Owner can grant access to other users (e.g., User B) with specific roles (Viewer, Editor).
- ๐ฐ Transactions: A Transaction belongs exclusively to one Broker.
- ๐ท๏ธ Names: Broker names are unique across the whole instance (
Broker.nameis a unique column), whoever owns them โ see Duplicate names. - ๐ Discovery: The brokers a user cannot access still appear by name on the Brokers page
(
BrokerDiscoveryCard, under Other Existing Brokers). Their sharing view is read-only:GET /brokers/{id}/accessloads the broker withas_user_id="all"and answers any signed-in user, so the user can see whom to ask for access. This is by design (developer decision, 2026-10-08): the users of one instance are not strangers, and seeing who has access lets them ask an Owner or choose whom to share with. The response carries each user's username, e-mail, role, share and avatar (BrokerService.list_accesses()); the frontend shows usernames and avatars, not e-mails.
๐ก๏ธ Broker Access Control (RBAC)
Access to brokers is granularly controlled via the BrokerUserAccess table.
๐ Deep Dive: For a detailed explanation of roles (OWNER, EDITOR, VIEWER) and permissions, see the Access Control (RBAC) documentation.
๐ Global vs. User-Specific Data
- ๐ค User-Specific:
User,UserSettings. - ๐ฆ Broker-Specific:
Broker,Transaction,BrokerUserAccess. - ๐ Global:
Asset,PriceHistory,FxRate,GlobalSettings.
Assets are global because the information about a financial instrument (like Apple stock) is the same for everyone. However, a user's transactions involving that asset are private (scoped to their Broker).