Skip to main content

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

EndpointMethodAuthRate LimitDescription
api/auth/loginPOSTAnonymous5 req/minAuthenticate user and issue JWT + refresh token
api/auth/registerPOSTAnonymous3 req/minCreate new user account and issue JWT + refresh
api/auth/meGETAuthorizedGet current user profile from JWT claims
api/auth/logoutPOSTAuthorizedRevoke token and clear session
api/auth/refreshPOSTAnonymousExchange refresh token for new JWT + refresh

Login Flow (AuthService.LoginAsync)

  1. Validation: Validate email and password are not empty.
  2. Normalization: Normalize email to lowercase and trim.
  3. Lookup: Find user by email via IUserRepository.GetByEmailAsync (case-insensitive).
  4. Verification: Verify password using BCrypt (PasswordService.VerifyPassword).
  5. Data Loading: Load user with details via IUserRepository.GetByIdWithDetailsAsync (includes Organization, UserRoles → Role → Permissions).
  6. Token Generation: Generate JWT token via JwtService.GenerateToken (includes Claims: NameIdentifier, Name, Email, Sub, Jti, organization_id, Role, permission[]).
  7. Refresh Generation: Generate refresh token via JwtService.GenerateRefreshToken.
  8. Session Storage: Store session in Redis via RedisService.SetUserSessionAsync (24h TTL).
  9. Refresh Storage: Store refresh token in Redis via RedisService.SetRefreshTokenAsync (30d TTL).
  10. Response: Return token + refresh token + user data (id, name, email, org, role with permissions).

Registration Flow (AuthService.RegisterAsync)

  1. Validation: Validate name, email, password (min 6 chars).
  2. Duplicate Check: Check for duplicate email via IUserRepository.EmailExistsAsync (case-insensitive).
  3. Organization Check: Verify organization exists via IUserRepository.OrganizationExistsAsync if OrganizationId provided.
  4. Hashing: Hash password with BCrypt (PasswordService.HashPassword).
  5. Entity Creation: Create user entity via IUserRepository.AddAsync and save.
  6. 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.
  7. Data Loading: Load user with details via IUserRepository.GetByIdWithDetailsAsync.
  8. Token Generation: Generate JWT token + refresh token.
  9. Session Storage: Store refresh token in Redis (30d TTL).
  10. Response: Return token + refresh token + user data.

Refresh Token Flow (AuthService.RefreshTokenAsync)

  1. Validation: Validate refresh token is not empty.
  2. Lookup: Look up user by matching refresh token in Redis using reverse index lookup (refresh_reverse:{token} → userId) for O(1) resolution.
  3. Data Loading: Load user with details (organization, roles, permissions).
  4. Token Generation: Generate new JWT token + new refresh token.
  5. Session Update: Rotate refresh token reverse index and store new session + refresh token in Redis.
  6. Response: Return new token + new refresh token + user data.

Logout Flow (AuthService.LogoutAsync)

  1. Extraction: Extract bearer token from Authorization header.
  2. User Identification: Extract user ID from JWT claims via UserHelper.GetUserId.
  3. Session Clearing: Delete user session from Redis (RedisService.DeleteUserSessionAsync).
  4. Blacklisting: Blacklist token in Redis (RedisService.BlacklistTokenAsync, 24h TTL).
  5. Refresh Clearing: Delete refresh token + reverse index for the user.
  6. 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:

ClaimSourceDescription
ClaimTypes.NameIdentifieruser.IdUser GUID
ClaimTypes.Nameuser.NameDisplay name
ClaimTypes.Emailuser.EmailEmail address
JwtRegisteredClaimNames.Subuser.IdStandard subject claim
JwtRegisteredClaimNames.Emailuser.EmailStandard email claim
JwtRegisteredClaimNames.JtiGuid.NewGuid()Unique token identifier
organization_idOrganization GUIDUser's organization
ClaimTypes.RoleRole nameUser's role name
permissionPermission slugsPolicy authorization claims

Token Configuration

SettingSourceDefault
SecretJwtSettings:Secret(required, ≥ 32 chars)
IssuerJwtSettings:IssuerRavenxcope.Backend
AudienceJwtSettings:AudienceRavenxcopeUsers
ExpiryJwtSettings:ExpiryMinutes1440 (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

EndpointMethodDescription
api/usersGETList users in current org
api/users/paginatedGETPaginated user list
api/users/{id}GETGet user by ID with permissions
api/usersPOSTCreate user with role
api/users/{id}PUTUpdate user details and role
api/users/{id}DELETEHard 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

  1. Validation: Validate name, email, password (min 6 chars).
  2. Duplicate Check: Check for duplicate email (case-insensitive).
  3. Verification: Verify organization and role exist if provided.
  4. Hashing: Hash password with BCrypt.
  5. Entity Creation: Create user entity.
  6. Role Assignment: If roleId provided, create UserRole junction record.
  7. Data Reloading: Reload user with navigation properties.
  8. Response: Return 201 Created with user data.

User Update

  1. Lookup: Find user by ID (404 if not found).
  2. Update Name: Update name if provided.
  3. Update Email: Update email if provided (check for duplicates).
  4. Update Password: Update password if provided (min 6 chars, hash with BCrypt).
  5. Update Organization: Update organization if provided.
  6. Update Role: If roleId provided:
    • Remove all existing UserRole records.
    • Create new UserRole record.
  7. 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 hash
  • VerifyPassword(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

BehaviorImplementation
Authentication enforcement[Authorize] attribute on controllers
Public endpoint access[AllowAnonymous] attribute on actions
Token blacklistingRedis-backed via JwtBearerEvents.OnTokenValidated
Session managementRedis with 24-hour TTL
Refresh token storageRedis with 30-day TTL
Password storageBCrypt hash (never stored as plaintext)
Authorization policiesmanage-users, manage-sensors, manage-assets, manage-roles, edit-organization
JWT secret warningStartup warning if secret < 32 characters
Rate limiting (login)Fixed window: 5 requests per minute
Rate limiting (register)Fixed window: 3 requests per minute
Response envelopeAll endpoints use ApiEnvelope.Success/Error
Exception handlingGlobal 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.