A production-oriented ASP.NET Core REST API demonstrating Clean / Hexagonal Architecture, JWT authentication with refresh token rotation, role & permission-based authorization, and maintainable backend engineering practices.
Built as a portfolio project to showcase backend skills beyond a simple CRUD API.
SecureHexagonalApi is a secure user-management API where business rules live in the Domain and Application layers, while HTTP, persistence, and security implementations stay in the outer layers.
The API focuses on:
- Strong separation of concerns (Domain / Application / Infrastructure / API)
- Secure authentication and session management
- Input validation and consistent error responses
- Audit logging for sensitive operations
- Unit tests on application use cases
This is intentionally clear and explainable in an interview, while still reflecting real-world backend patterns.
- JWT authentication with short-lived access tokens
- Refresh tokens with rotation and anti-replay protection
- Access token revocation via JWT blacklist (logout)
- Role & permission-based authorization (
HasPermissionattribute) - Active session management per user
- API versioning (
v1/v2) - FluentValidation on request DTOs
- Centralized error handling with RFC 7807 ProblemDetails
- Audit logging for write operations
- Health check endpoint (
/health) - Swagger / OpenAPI with Bearer JWT support
- Unit tests for core Application handlers
The solution follows Clean / Hexagonal Architecture:
┌──────────────────────────────────────────────┐
│ API │
│ Controllers, DTOs, Middleware, Swagger │
└──────────────────────┬───────────────────────┘
│
┌──────────────────────▼───────────────────────┐
│ Application │
│ Use cases (Handlers), Ports, DTOs │
└──────────────────────┬───────────────────────┘
│
┌──────────────────────▼───────────────────────┐
│ Domain │
│ Entities, Value Objects, Business Rules │
└──────────────────────┬───────────────────────┘
│
┌──────────────────────▼───────────────────────┐
│ Infrastructure │
│ EF Core, Repositories, JWT, Audit Logger │
└──────────────────────────────────────────────┘
Dependency rule: inner layers never depend on outer layers.
The Application layer defines ports (interfaces); Infrastructure provides adapters.
| Layer | Technologies |
|---|---|
| API | ASP.NET Core 8, Swagger, API Versioning |
| Application | Handlers (use cases), FluentValidation |
| Domain | Entities, Value Objects, Domain Exceptions |
| Infrastructure | Entity Framework Core, SQL Server, BCrypt, JWT |
| Tests | xUnit |
- Password hashing with BCrypt
- JWT signed with HMAC-SHA256
- Refresh token rotation on every refresh request
- Revoked refresh tokens cannot be reused
- Access tokens can be blacklisted on logout
- Permission-based authorization at endpoint level
- No secrets committed in source code (configuration-driven)
- Request validation before reaching application logic
1. POST /api/v1/auth/login
└─> Returns access token + refresh token
2. Authenticated requests
└─> Authorization: Bearer {access_token}
3. POST /api/v1/auth/refresh
└─> Sends refresh token
└─> Old refresh token is revoked
└─> New access token + new refresh token are returned
4. POST /api/v1/auth/logout
└─> Refresh token revoked
└─> Current access token JTI added to blacklist
5. POST /api/v1/auth/logout-all
└─> All refresh tokens revoked for the user
Session metadata (IP address, user agent) is stored with refresh tokens to support session inspection.
SecureHexagonalApi/
├── SecureHexagonalApi.API/ # HTTP layer (controllers, DTOs, middleware)
├── SecureHexagonalApi.Application/ # Use cases, ports, application DTOs
├── SecureHexagonalApi.Domain/ # Core business model
├── SecureHexagonalApi.Infrastructure/# EF Core, repositories, JWT, audit
└── SecureHexagonalApi.Tests/ # Unit tests (Application & Domain)
Versioning is URL-based:
api/v1/...— stable endpointsapi/v2/...— evolved response format (e.g. wrapped API responses on login)
Swagger exposes a document per API version.
Unhandled exceptions are converted to RFC 7807 ProblemDetails:
| Scenario | HTTP Status | Response |
|---|---|---|
| FluentValidation failure | 400 | ValidationProblemDetails |
| Business rule violation | 400 / 401 / 404 | ProblemDetails |
| Unexpected server error | 500 | ProblemDetails |
Example:
{
"title": "Validation error",
"status": 400,
"errors": {
"Email": ["Invalid email format."]
}
}Request DTOs are validated at the API boundary:
LoginRequestRefreshTokenRequestLogoutRequestCreateUserRequestUpdateUserRequestAssignRoleRequestAssignPermissionRequest
Controllers stay thin and delegate business logic to Application handlers.
Sensitive write operations (user creation, role assignment, etc.) are logged through IAuditLogger.
Audit logs can be queried via the protected audit endpoints (requires audit.read permission).
Tests focus on Application handlers with fake repositories and services:
AuthenticateUserHandlerTestsRefreshTokenHandlerTestsCreateUserHandlerTests- Auth session / logout handler tests
- Domain value object tests (
Email)
Run tests:
dotnet test- .NET 8 SDK
- SQL Server or LocalDB
- (Optional) EF Core CLI tools
git clone https://github.com/anassrh/SecureHexagonalApi.git
cd SecureHexagonalApi
cp SecureHexagonalApi.API/appsettings.Development.example.json SecureHexagonalApi.API/appsettings.Development.jsonConfiguration is loaded from appsettings.json, appsettings.Development.json, and environment variables.
| Key | Description |
|---|---|
ConnectionStrings__DefaultConnection |
SQL Server connection string |
Jwt__Secret |
JWT signing secret (min. 32 characters) |
Jwt__AccessTokenExpirationMinutes |
Access token lifetime (default: 30) |
Jwt__RefreshTokenExpirationDays |
Refresh token lifetime (default: 7) |
Seed__AdminEmail |
Default admin email for seeding |
Seed__AdminPassword |
Default admin password for seeding |
Never commit production secrets. Use User Secrets locally or environment variables in deployment.
cd SecureHexagonalApi.API
dotnet user-secrets init
dotnet user-secrets set "ConnectionStrings:DefaultConnection" "Server=(localdb)\\mssqllocaldb;Database=SecureHexagonalApiDb;Trusted_Connection=True;TrustServerCertificate=True"
dotnet user-secrets set "Jwt:Secret" "your-local-secret-key-at-least-32-chars"dotnet restore
dotnet ef database update --project SecureHexagonalApi.Infrastructure --startup-project SecureHexagonalApi.API
dotnet run --project SecureHexagonalApi.APIThen open:
- Swagger UI:
https://localhost:{port}/swagger - Health check:
https://localhost:{port}/health
Default seeded admin (Development):
- Email:
admin@localhost - Password:
Admin123!
dotnet test- Integration tests with
WebApplicationFactory - Rate limiting on auth endpoints
- Structured logging with Serilog
- Docker Compose (API + SQL Server)
- CI/CD pipeline (GitHub Actions)
- Redis-backed token blacklist for distributed deployments
Anass Rhouzladi
This project is available for portfolio and learning purposes.