A lightweight personal-finance Web API built with ASP.NET Core (.NET 8) and Entity Framework Core. It provides secure authentication (JWT + refresh tokens), domain-driven organization, and CRUD support for accounts, categories, incomes, expenses, savings, and future expenses.
The API is designed to be consumed by a React frontend and exposes interactive documentation via Swagger in Development.
- Target framework: .NET 8, C# 12
- Architecture: Layered (API / Application / Domain / Infrastructure)
- Authentication: ASP.NET Core Identity with JWT access tokens and secure refresh tokens (Read more)
- Persistence: Entity Framework Core (SQL Server by default)
- Migrations:
ExpenseTracker.Infrastructure/Migrations - API docs: Swagger UI enabled in Development via
AddCustomSwagger()/UseCustomSwagger(...) - CORS:Dedicated policy for React frontend (
ReactCors)
ExpenseTracker/
├── ExpenseTracker.API
│ ├── Controllers
│ ├── Extensions (Swagger, CORS, Auth)
│ └── Program.cs
│
├── ExpenseTracker.Application
│ ├── DTOs
│ ├── Commands / Queries
│ └── Business rules
│
├── ExpenseTracker.Domain
│ ├── Entities
│ └── Interfaces
│
├── ExpenseTracker.Infrastructure
│ ├── Persistence (DbContext)
│ ├── Identity
│ └── Migrations
│
└── ExpenseTracker.Shared
├── Enums
├── Shared DTOs
└── Constants- .NET 8 SDK
- SQL Server (or another EF Core provider if adapted)
Primary configuration lives in ExpenseTracker.API/appsettings.json. Important keys:
ConnectionStrings:DefaultConnection— EF Core connection stringJwt:Key— Signing key (DO NOT commit to source control)Jwt:Issuer,Jwt:Audience— Token issuer and audienceJwt:ExpiresMinutes— JWT lifetime (minutes)RefreshToken:Pepper(optional) — Optional server-side pepper for refresh token hashingRefreshToken:Longevity— Refresh token longevity (days)
{
"ConnectionStrings":
{
"DefaultConnection": "Server=localhost; Database=ExpenseTracker; Trusted_Connection=True; TrustServerCertificate=True";
},
"Jwt":
{
"Key": "<YOUR_SECRET_KEY>",
"Issuer": "https://localhost:42069",
"Audience": "https://localhost:6969",
},
"RefreshToken":
{
"Pepper": "<OPTIONAL_PEPPER>"
"Longevity": 14
}
}Use dotnet user-secrets or OS environment variables to store secrets during development.
Using Visual Studio 2026:
- Open the solution
- Set
ExpenseTracker.APIas the Startup Project. - Choose a launch profile (for example, Kestrel or IIS Express).
- Add connection string and JWT secrets to
appsettings.jsonor dotnet user-secrets. - Apply migrations
- Run the project.
Update-Database -Context ApplicationDbContextor if that doesn't work .NET CLI
dotnet ef database update --project ExpenseTracker.Infrastructuredotnet restore
dotnet build
dotnet run --project ExpenseTracker.APIAnd run the app, After startup, Swagger is available in Development at https://localhost:{port}/swagger.
- Access tokens:
- JWT signed with
Jwt:Key. - Short-lived, sent via
Authorization: Bearer <token>
- JWT signed with
- Refresh tokens:
- Generated by
TokenUtil.GenerateRefreshToken - Sent to client as an HttpOnly, Secure cookie.
- Only the hashed token is stored in the database.
- Optional server-side pepper can be applied before hashing.
- Generated by
This design prevents token theft and ensures long-lived sessions without exposing anything.
Swagger is registered via
AddCustomSwagger()UseCustomSwagger(...)
Swagger is enabled only in Development. Use Swagger UI for endpoint discovery and testing.
A policy named ReactCors is registered for the React frontend. Configure allowed origins in the API startup or configuration extension.
This repository contains only the backend API. As this is supposed to be a full-stack Expense Tracker application. The frontend is maintaned in a different repository:
Repository: https://github.com/Alp-Balaj/expense-tracker-front
Refer to the frontend repository's README for setup instructions, environment variables, and so on...
The frontend is responsible for:
- User authentication and session handling
- Entity and reporting UI
- Communicating with this API via HTTPS (JWT + refresh tokens)
To run the full application locally, clone both repositories, start the API first, then run the React frontend and point it to the API base URL.