A standalone authentication and security REST API project. Provides local email/password authentication, Google OAuth2, session management, and SMTP configuration through a clean, layered architecture.
| Concern | Technology |
|---|---|
| Runtime | .NET 8 |
| ORM | Entity Framework Core 8 + Npgsql |
| Database | PostgreSQL |
| Auth | JWT Bearer (HttpOnly cookie + Authorization header fallback) |
| Password hashing | BCrypt.Net-Next |
| MailKit | |
| Validation | FluentValidation |
| API docs | Swagger / OpenAPI (Swashbuckle) |
The solution follows Clean Architecture with a strict unidirectional dependency rule:
Common <-- DTO <-- Domain <-- Application <-- API
^ ^ ^
Infrastructure -------+--------------
| Project | Responsibility |
|---|---|
AuthGuard.Common |
Enums, helpers, Optional<T> — zero dependencies |
AuthGuard.DTO |
Positional records for request/response — no Domain references |
AuthGuard.Domain |
Entities, repository interfaces, Result<T>, IUnitOfWork |
AuthGuard.Application |
Service interfaces and implementations, validators, error messages |
AuthGuard.Infrastructure |
EF Core, repositories, JWT, BCrypt, MailKit |
AuthGuard.API |
Controllers, middleware, DI wiring |
- .NET 8 SDK
- PostgreSQL 14+
1. Clone the repository
git clone https://github.com/lgimenez-dev/auth-guard.git
cd auth-guard2. Create the database
Create the database and run the init script. It creates the schema, indexes, and seeds the initial admin user (password: 1234):
sudo -u postgres psql -c "CREATE DATABASE authguard;"
sudo -u postgres psql -d authguard -f scripts/init.sql3. Configure the application
Edit AuthGuard.API/appsettings.Development.json and replace the placeholders.
To generate a cryptographically secure value for "Jwt.Secret", just run openssl rand -hex 32
{
"ConnectionStrings": {
"Default": "Host=localhost;Port=5432;Database=authguard;Username=YOUR_DB_USER;Password=YOUR_DB_PASSWORD"
},
"Jwt": {
"Secret": "YOUR_JWT_SECRET_MIN_32_CHARACTERS_LONG",
"ExpirationMinutes": 60
},
"Google": {
"ClientId": "YOUR_GOOGLE_CLIENT_ID",
"ClientSecret": "YOUR_GOOGLE_CLIENT_SECRET"
},
"App": {
"Name": "AuthGuard",
"FrontendUrl": "http://localhost:3000"
}
}Google credentials are only required if you intend to use the OAuth2 endpoints.
4. Run the API
dotnet run --project AuthGuard.APISwagger UI is available at https://localhost:{port}/swagger in Development mode.
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | /api/auth/register |
Public | Creates account (Pending status) and sends activation email |
| POST | /api/auth/login |
Public | Authenticates user, sets access_token HttpOnly cookie. Rate-limited to 5 req/min |
| POST | /api/auth/logout |
JWT | Revokes the current session and deletes the cookie |
| GET | /api/auth/validate-token |
JWT | Confirms the session is active in the database |
| GET | /api/auth/me |
JWT | Returns identity claims from the JWT (no DB hit) |
| POST | /api/auth/recover-password |
Public | Sends a recovery email (always returns 200 to prevent enumeration) |
| POST | /api/auth/change-password |
Public | Sets a new password via one-time token (covers activation and recovery) |
| GET | /api/auth/oauth2/google |
Public | Redirects to Google consent screen |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /api/sessions/user/{userId} |
JWT | Lists active sessions for a user |
| POST | /api/sessions/user/{userId}/revoke |
JWT | Revokes all active sessions for a user |
| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | /api/smtp-config |
JWT (Backoffice) | Returns the current SMTP configuration |
| POST | /api/smtp-config |
JWT (Backoffice) | Creates the SMTP configuration (single record) |
| PUT | /api/smtp-config |
JWT (Backoffice) | Replaces all SMTP configuration fields |
Local registration and activation
POST /register --> account created (Pending) --> activation email sent
POST /change-password (with token) --> account activated --> ready to login
Password recovery
POST /recover-password --> recovery email sent (if account exists and national ID matches)
POST /change-password (with token) --> new password set
Google OAuth2
GET /api/auth/oauth2/google --> redirect to Google --> callback handled by middleware
--> access_token cookie set --> redirect to FrontendUrl
On the first Google login, a new account is created as Active (no activation step). If the email already exists under a local account, the login is rejected to prevent account hijacking.
- JWTs are issued on login and stored server-side as a SHA-256 hash in
user_sessions. - The token is read from the
access_tokenHttpOnly cookie first, falling back to theAuthorization: Bearerheader. GET /api/auth/validate-tokenverifies the session is still active in the database (not just the JWT signature).- Logging in revokes all previous active sessions for the user.
The database seed script creates an initial Backoffice user:
| Field | Value |
|---|---|
admin@authapp.com |
|
| Password | 1234 |
| Role | Backoffice |
Change this password after first login.
AuthGuard/
├── AuthGuard.Common/
│ ├── Enums/ UserRole, UserStatus, UserProvider
│ ├── Helpers/ NumberHelper, StringHelper
│ └── Optional.cs
├── AuthGuard.DTO/ Positional records for all request/response types
├── AuthGuard.Domain/
│ ├── Entities/ User, Person, UserSession, SmtpConfig
│ ├── Repositories/ IUserRepository, IPersonRepository, ...
│ ├── IUnitOfWork.cs
│ └── Result.cs
├── AuthGuard.Application/
│ ├── Auth/ IAuthService, AuthService, validators
│ ├── OAuth2/ IOAuth2Service, OAuth2Service
│ ├── Sessions/ IUserSessionService, UserSessionService
│ ├── SmtpConfig/ ISmtpConfigService, SmtpConfigService, validators
│ ├── Services/ IJwtService, IPasswordService, IEmailService
│ ├── Helpers/ HmacHelper, EmailHelper
│ └── ErrorMessages.cs
├── AuthGuard.Infrastructure/
│ ├── Configurations/ EF Core entity configurations
│ ├── Repositories/ EF Core repository implementations
│ ├── Services/ JwtService, PasswordService, EmailService
│ └── UnitOfWork.cs
└── AuthGuard.API/
├── Controllers/ AuthController, UserSessionController, SmtpConfigController
├── Extensions/ JwtBearerExtensions, GoogleAuthExtensions, SwaggerExtensions, CorsExtensions
├── Middleware/ ExceptionHandlingMiddleware
├── Models/ BusinessErrorResponse, ValidationErrorResponse
└── Program.cs
Result pattern over exceptions — services return Result<T> for expected business failures. Exceptions are only thrown for truly unexpected errors and are caught by ExceptionHandlingMiddleware.
No FromEntity on DTOs — mapping from entities to DTOs is done in private static helpers inside each service. DTOs have no dependency on Domain.
Session-based JWT tracking — every issued JWT has a corresponding record in user_sessions. This allows true server-side revocation beyond JWT expiration.
Enum storage — C# enums use PascalCase (Member, Active, Local). EF Core value converters store them as uppercase strings in PostgreSQL (MEMBER, ACTIVE, LOCAL).