Authentication and Users
Introduction
This document covers the authentication and user management features of the Ravenxcope backend. Authentication is handled via JWT tokens with refresh token support, Redis-backed session management, and token blacklisting. User management provides CRUD operations scoped to organizations with role assignment. All business logic is encapsulated in AuthService and UserService, with controllers acting as thin HTTP transport layers.
Authentication Module
Controller
File: API/Controllers/Auth/AuthController.cs
Route: api/auth
Inherits: ControllerBase
Delegates to: IAuthService (all business logic)
Endpoints
| Endpoint | Method | Auth | Rate Limit | Description |
|---|---|---|---|---|
api/auth/login | POST | Anonymous | 5 req/min | Authenticate user and issue JWT + refresh token |
api/auth/register | POST | Anonymous | 3 req/min | Create new user account and issue JWT + refresh |
api/auth/me | GET | Authorized | — | Get current user profile from JWT claims |
api/auth/logout | POST | Authorized | — | Revoke token and clear session |
api/auth/refresh | POST | Anonymous | — | Exchange refresh token for new JWT + refresh |
Login Flow (AuthService.LoginAsync)
- Validation: Validate email and password are not empty.
- Normalization: Normalize email to lowercase and trim.
- Lookup: Find user by email via
IUserRepository.GetByEmailAsync(case-insensitive). - Verification: Verify password using BCrypt (
PasswordService.VerifyPassword). - Data Loading: Load user with details via
IUserRepository.GetByIdWithDetailsAsync(includes Organization, UserRoles → Role → Permissions). - Token Generation: Generate JWT token via
JwtService.GenerateToken(includes Claims: NameIdentifier, Name, Email, Sub, Jti, organization_id, Role, permission[]). - Refresh Generation: Generate refresh token via
JwtService.GenerateRefreshToken. - Session Storage: Store session in Redis via
RedisService.SetUserSessionAsync(24h TTL). - Refresh Storage: Store refresh token in Redis via
RedisService.SetRefreshTokenAsync(30d TTL). - Response: Return token + refresh token + user data (id, name, email, org, role with permissions).
Registration Flow (AuthService.RegisterAsync)
- Validation: Validate name, email, password (min 6 chars).
- Duplicate Check: Check for duplicate email via
IUserRepository.EmailExistsAsync(case-insensitive). - Organization Check: Verify organization exists via
IUserRepository.OrganizationExistsAsyncif OrganizationId provided. - Hashing: Hash password with BCrypt (
PasswordService.HashPassword). - Entity Creation: Create user entity via
IUserRepository.AddAsyncand save. - Role Assignment: If RoleId provided:
- Find role by ID via
IRoleRepository.GetByIdWithPermissionsAsync. - If role is "Super Admin" or "Admin" and has no permissions, auto-assign ALL permissions from the catalog.
- Create UserRole junction record via
IUserRoleRepository.AddAsync.
- Find role by ID via
- Data Loading: Load user with details via
IUserRepository.GetByIdWithDetailsAsync. - Token Generation: Generate JWT token + refresh token.
- Session Storage: Store refresh token in Redis (30d TTL).
- Response: Return token + refresh token + user data.
Refresh Token Flow (AuthService.RefreshTokenAsync)
- Validation: Validate refresh token is not empty.
- Lookup: Look up user by matching refresh token in Redis using reverse index lookup (
refresh_reverse:{token}→ userId) for O(1) resolution. - Data Loading: Load user with details (organization, roles, permissions).
- Token Generation: Generate new JWT token + new refresh token.
- Session Update: Rotate refresh token reverse index and store new session + refresh token in Redis.
- Response: Return new token + new refresh token + user data.
Logout Flow (AuthService.LogoutAsync)
- Extraction: Extract bearer token from Authorization header.
- User Identification: Extract user ID from JWT claims via
UserHelper.GetUserId. - Session Clearing: Delete user session from Redis (
RedisService.DeleteUserSessionAsync). - Blacklisting: Blacklist token in Redis (
RedisService.BlacklistTokenAsync, 24h TTL). - Refresh Clearing: Delete refresh token + reverse index for the user.
- Response: Return success message.
Token Blacklist Enforcement
Token blacklisting is enforced at the authentication middleware level via JwtBearerEvents.OnTokenValidated:
options.Events = new JwtBearerEvents
{
OnTokenValidated = async context =>
{
var redisService = context.HttpContext.RequestServices
.GetRequiredService<IRedisService>();
var token = BearerTokenHelper.GetBearerToken(context.Request);
if (string.IsNullOrWhiteSpace(token))
{
context.Fail("Token is missing or invalid");
return;
}
if (await redisService.IsTokenBlacklistedAsync(token))
{
context.Fail("Token has been revoked");
}
}
};
This runs on every authenticated request, ensuring revoked tokens are immediately rejected.
JWT Service
File: Infrastructure/Services/JwtService.cs
Lifetime: Singleton
Token Generation
The JwtService generates JWT tokens with these claims:
| Claim | Source | Description |
|---|---|---|
ClaimTypes.NameIdentifier | user.Id | User GUID |
ClaimTypes.Name | user.Name | Display name |
ClaimTypes.Email | user.Email | Email address |
JwtRegisteredClaimNames.Sub | user.Id | Standard subject claim |
JwtRegisteredClaimNames.Email | user.Email | Standard email claim |
JwtRegisteredClaimNames.Jti | Guid.NewGuid() | Unique token identifier |
organization_id | Organization GUID | User's organization |
ClaimTypes.Role | Role name | User's role name |
permission | Permission slugs | Policy authorization claims |
Token Configuration
| Setting | Source | Default |
|---|---|---|
| Secret | JwtSettings:Secret | (required, ≥ 32 chars) |
| Issuer | JwtSettings:Issuer | Ravenxcope.Backend |
| Audience | JwtSettings:Audience | RavenxcopeUsers |
| Expiry | JwtSettings:ExpiryMinutes | 1440 (24 hours) |
| Algorithm | (hardcoded) | HmacSha256 |
| Clock Skew | (hardcoded) | TimeSpan.Zero |
Validation
The ValidateToken method and JWT Bearer middleware enforce:
- Issuer validation (must match configured issuer)
- Audience validation (must match configured audience)
- Lifetime validation (token must not be expired)
- Signing key validation (HMAC-SHA256 with symmetric key)
- Zero clock skew (strict time matching)
User Management Module
Controller
File: API/Controllers/Users/UsersController.cs
Route: api/users
Inherits: ControllerBase
Auth: [Authorize] (all endpoints require authentication)
Delegates to: IUserService (all business logic)
Endpoints
| Endpoint | Method | Description |
|---|---|---|
api/users | GET | List users in current org |
api/users/paginated | GET | Paginated user list |
api/users/{id} | GET | Get user by ID with permissions |
api/users | POST | Create user with role |
api/users/{id} | PUT | Update user details and role |
api/users/{id} | DELETE | Hard delete user |
Organization Scoping
User listing endpoints extract the organization ID from JWT claims:
var organizationId = UserHelper.GetOrganizationId(User);
if (!organizationId.HasValue)
{
return BadRequest(ApiEnvelope.Error("Organization ID not found in token", HttpContext.TraceIdentifier));
}
var users = await _userService.GetUsersAsync(organizationId.Value);
Pagination
The GET api/users/paginated endpoint supports query-parameter pagination:
GET /api/users/paginated?page=1&pageSize=10
Response includes pagination metadata:
{
"success": true,
"data": {
"items": [...],
"pagination": {
"currentPage": 1,
"pageSize": 10,
"totalCount": 42,
"totalPages": 5
}
},
"errors": []
}
User Creation
- Validation: Validate name, email, password (min 6 chars).
- Duplicate Check: Check for duplicate email (case-insensitive).
- Verification: Verify organization and role exist if provided.
- Hashing: Hash password with BCrypt.
- Entity Creation: Create user entity.
- Role Assignment: If roleId provided, create UserRole junction record.
- Data Reloading: Reload user with navigation properties.
- Response: Return 201 Created with user data.
User Update
- Lookup: Find user by ID (404 if not found).
- Update Name: Update name if provided.
- Update Email: Update email if provided (check for duplicates).
- Update Password: Update password if provided (min 6 chars, hash with BCrypt).
- Update Organization: Update organization if provided.
- Update Role: If roleId provided:
- Remove all existing UserRole records.
- Create new UserRole record.
- Response: Return updated user data.
User Deletion
Users are hard deleted (not soft deleted). Cascade delete rules handle related UserRole records automatically.
Password Hashing
File: Infrastructure/Services/PasswordService.cs
Library: BCrypt.Net-Next
The service provides static methods:
HashPassword(string password)— BCrypt hashVerifyPassword(string password, string hash)— BCrypt verify
Helper Classes
BearerTokenHelper
File: Common/Helpers/BearerTokenHelper.cs
Extracts the JWT token from the Authorization: Bearer {token} header:
public static string? GetBearerToken(HttpRequest request)
UserHelper
File: Common/Helpers/UserHelper.cs
Extracts claims from the authenticated user principal:
public static Guid? GetOrganizationId(ClaimsPrincipal user)
public static Guid? GetUserId(ClaimsPrincipal user)
Security Behavior
| Behavior | Implementation |
|---|---|
| Authentication enforcement | [Authorize] attribute on controllers |
| Public endpoint access | [AllowAnonymous] attribute on actions |
| Token blacklisting | Redis-backed via JwtBearerEvents.OnTokenValidated |
| Session management | Redis with 24-hour TTL |
| Refresh token storage | Redis with 30-day TTL |
| Password storage | BCrypt hash (never stored as plaintext) |
| Authorization policies | manage-users, manage-sensors, manage-assets, manage-roles, edit-organization |
| JWT secret warning | Startup warning if secret < 32 characters |
| Rate limiting (login) | Fixed window: 5 requests per minute |
| Rate limiting (register) | Fixed window: 3 requests per minute |
| Response envelope | All endpoints use ApiEnvelope.Success/Error |
| Exception handling | Global handler — no try-catch in controllers |
Future Improvements
Completed
- Standardized response envelope (
ApiEnvelope). - Refresh token mechanism (
POST api/auth/refresh). - Business logic moved to service layer (
AuthService,UserService). - Rate limiting for login/register endpoints (fixed-window policy).
Open
- Add dedicated integration tests for token revoke and refresh edge cases.