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/meGETAuthorized—Get current user profile from JWT claims
api/auth/logoutPOSTAuthorized—Revoke token and clear session
api/auth/refreshPOSTAnonymous—Exchange 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.