Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Budget
A multi-user budget web app built around the 50/30/20 framework. Users manage Income and Outgo entries, view a Summary breakdown by type (Need / Want / Save), and can share budgets with other users in edit or view-only mode.
Stack: ASP.NET Core 10 + Vite/React (TypeScript) — served as a single Docker container. PostgreSQL via EF Core. OIDC authentication against auth.stwaddle.com.
Environment Variables
Backend (ASP.NET Core)
| Variable | Required | Description |
|---|---|---|
ConnectionStrings__DefaultConnection |
Yes | Full Npgsql connection string |
AUTH__AUTHORITY |
Yes | OIDC authority base URL (trailing slash required) |
AUTH__AUDIENCE |
Yes | Expected aud claim in access tokens — must match the scope's Resource value in the auth server |
ASPNETCORE_ENVIRONMENT |
No | Production or Development (default: Production in Docker) |
If ConnectionStrings__DefaultConnection is absent, the app falls back to assembling a connection string from individual variables:
| Variable | Default | Description |
|---|---|---|
POSTGRES_HOST |
localhost |
Database host |
POSTGRES_PORT |
5432 |
Database port |
POSTGRES_DB |
budget |
Database name |
POSTGRES_USER |
budget |
Database user |
POSTGRES_PASSWORD |
changeme |
Database password |
Frontend (Vite — baked in at build time)
These are compiled into the static assets during the Docker build and cannot be changed at runtime.
| Variable | Description |
|---|---|
VITE_AUTH_AUTHORITY |
OIDC authority base URL — must match AUTH__AUTHORITY |
VITE_AUTH_CLIENT_ID |
OIDC client ID registered in the auth server |
VITE_AUTH_REDIRECT_URI |
Full URL of the /callback route (e.g. https://budget.stwaddle.com/callback) |
To change frontend auth config after the image is built, rebuild the image with updated build args. See the Dockerfile.
Production Deployment
The service is defined in docker/docker-compose.yaml and managed alongside the rest of the stwaddle.com infrastructure.
docker-compose service
budget:
image: docker.stwaddle.com/budget:latest
environment:
- VIRTUAL_HOST=budget.stwaddle.com
- LETSENCRYPT_HOST=budget.stwaddle.com
- VIRTUAL_PORT=8080
- ASPNETCORE_ENVIRONMENT=Production
- ConnectionStrings__DefaultConnection=Host=apps-db;Port=5432;Database=budget;Username=postgres;Password=${APPS_DB_PASSWORD}
- AUTH__AUTHORITY=https://auth.stwaddle.com/
- AUTH__AUDIENCE=budget-api
depends_on:
- apps-db
- auth
networks:
- web
- apps-internal
restart: unless-stopped
.env entries
Add to the .env file on the server (alongside the existing entries):
BUDGET_VIRTUAL_HOST=budget.stwaddle.com
BUDGET_LETSENCRYPT_HOST=budget.stwaddle.com
APPS_DB_PASSWORD is already set in .env for the recipes app and is shared. The budget app gets its own budget database within the same Postgres instance — EF Core's MigrateAsync() creates it automatically on first startup.
Build and push
.\build-budget-image.ps1
This builds the multi-stage Docker image (Node → .NET SDK → ASP.NET runtime) and pushes it to docker.stwaddle.com/budget:latest.
The VITE_* build args default to the production values in the Dockerfile. To override:
docker build `
--build-arg VITE_AUTH_AUTHORITY=https://auth.stwaddle.com/ `
--build-arg VITE_AUTH_CLIENT_ID=budget-client `
--build-arg VITE_AUTH_REDIRECT_URI=https://budget.stwaddle.com/callback `
-t docker.stwaddle.com/budget:latest .
docker push docker.stwaddle.com/budget:latest
DNS
Add an A record for budget.stwaddle.com pointing to the Linode server IP before deploying. The acme-companion will issue the Let's Encrypt certificate automatically. If DNS hasn't propagated when the container first starts:
docker compose restart acme-companion
Auth Server Setup
The budget app requires two registrations in the auth server admin panel at https://auth.stwaddle.com/Admin/ (requires an account with the Admin role).
1. Create the API Scope
Admin → Scopes → Create
| Field | Value |
|---|---|
| Name | budget_api |
| Display Name | Budget API |
| Resources | budget-api |
The Resources field is what gets written into the aud claim of access tokens. It must exactly match AUTH__AUDIENCE=budget-api set on the API container.
2. Register the Client Application
Admin → Sites → Create
| Field | Value |
|---|---|
| Client ID | budget-client |
| Display Name | Budget |
| Redirect URIs | https://budget.stwaddle.com/callback |
| Post-Logout Redirect URIs | https://budget.stwaddle.com |
| API Scopes | ✅ budget_api |
The client is always created as Public (no secret) with Authorization Code + PKCE + Refresh Token — this is enforced by the auth server for all SPA clients.
For local development, also add the dev URIs (you can edit the client after creation):
- Redirect URI:
http://localhost:5173/callback - Post-Logout URI:
http://localhost:5173
3. Grant Users Access
Users must be explicitly granted a role on the client before they can log in.
Admin → Sites → [Budget] → Roles → Grant
Assign any user who needs access at least the user role. Without a role entry the auth server returns access_denied at the authorize endpoint.
Token details
| Property | Value |
|---|---|
| Flow | Authorization Code + PKCE |
| Access token lifetime | 15 minutes |
| Refresh token lifetime | 14 days |
| Claims in access token | sub, email, name, role, aud (budget-api) |
The frontend uses oidc-client-ts with automatic silent renewal, so users stay logged in across the 15-minute access token window without re-authenticating.
Local Development
Prerequisites
- .NET 10 SDK
- Node 22
- PostgreSQL (or Docker)
Setup
-
Copy
.env.exampleto.envand fill in values for your local Postgres instance. -
Start the API:
cd src/Budget.Api dotnet runThe API listens on
http://localhost:5000by default. EF migrations run automatically on startup. -
Start the frontend dev server:
cd src/Budget.Client npm install npm run devVite proxies
/apirequests tohttp://localhost:5000. The app is available athttp://localhost:5173. -
Set the following in
src/Budget.Client/.env.local(Vite picks this up automatically, it is gitignored):VITE_AUTH_AUTHORITY=https://auth.stwaddle.com/ VITE_AUTH_CLIENT_ID=budget-client VITE_AUTH_REDIRECT_URI=http://localhost:5173/callback -
Set the following in
src/Budget.Api/appsettings.Development.json:{ "AUTH__AUTHORITY": "https://auth.stwaddle.com/", "AUTH__AUDIENCE": "budget-api" }
The auth server's dev redirect URI (http://localhost:5173/callback) must be registered on the client as described in the Auth Server Setup section above.