A REST API for a small publishing platform — posts, categories, tags and users — built on .NET 10 with ASP.NET Core and Entity Framework Core, and intended to be consumed by a Next.js front end.
Previously
CyberSecurityNextApi. Neither half of that name described the code: it began as an MVC application for a site whose articles were about cyber security, and Next meant the Next.js client it was rebuilt for, not a second version. Renamed for the obvious reason. GitHub redirects the old URL.
| Layer | Choice |
|---|---|
| Runtime | .NET 10 |
| Web | ASP.NET Core, controller-based |
| Data | Entity Framework Core 10, SQL Server |
| Auth | JWT bearer, HMAC-SHA512 signing |
| Mapping | AutoMapper 15 |
| Docs | Swashbuckle / Swagger UI |
Each aggregate has an interface and an implementation under Services/, and every
method returns ServiceResponse<T> — a small envelope carrying Data, Success
and Message — so controllers stay thin and a failure is a value rather than an
exception crossing a layer boundary.
Controllers/ HTTP surface. No business logic.
Services/ One folder per capability, interface alongside implementation.
Data/ DbContext and the auth repository.
Dtos/ Request and response shapes, split by aggregate.
Models/ EF entities.
Migrations/ Schema history.
Passwords are stored as an HMAC-SHA512 hash with a per-user salt — never the password itself — and compared in fixed time.
Requires the .NET 10 SDK and a SQL Server instance (Express or LocalDB is fine).
1. Set the JWT signing key. It is deliberately not in appsettings.json, and
the application refuses to start without it:
dotnet user-secrets init dotnet user-secrets set "AppSettings:Token" "<a long random string>"
Or set AppSettings__Token in the environment.
2. Point it at a database. ConnectionStrings:DefaultConnection in
appsettings.json defaults to .\SQLEXPRESS with a trusted connection. Override
it the same way as the key if yours differs.
3. Create the schema and run:
dotnet ef database update dotnet run
Swagger UI is at /swagger in development.
All routes are under /api. Everything requires a bearer token except
POST /api/User/Login.
| Method | Route | Purpose |
|---|---|---|
POST |
/api/User/Login |
Exchange credentials for a token |
POST |
/api/User/Register |
Create a user |
GET |
/api/User/GetAll |
List users |
GET |
/api/User/{id} |
One user |
PUT |
/api/User/Update |
Update a user |
DELETE |
/api/User/{id} |
Remove a user |
/api/Categories, /api/Posts, /api/Roles |
The same shape per aggregate |
Registration sits behind authentication on purpose: users are created by an
existing account, and the creator is recorded in CreatedBy.
- Login answers the same way for an unknown user and a wrong password. Distinguishing them tells an attacker which addresses are registered.
- Updating a user only changes the password when one is supplied. An update that omits it leaves the existing credential alone.
- Email addresses are normalised on registration, on update and on lookup, so the same address cannot register twice in different cases.
- Timestamps and token expiry are UTC.
MIT.