Authentication¶
Overview¶
The authentication feature handles all aspects of user identity in Vitara, including account registration, email/password login, multi-factor authentication (TOTP), password expiry enforcement, forgot/reset password flows, and JWT issuance. Every request to a protected API endpoint is authorised via a JWT Bearer token. The JWT embeds the user's id, email, role, termId (active cycle), firstName, and lastName claims.
Roles¶
| ID | Name | Description |
|---|---|---|
| 1 | SuperAdmin | Full system access |
| 2 | Administrator | Manages users, config, reports |
| 3 | Carer | Links to care recipients, records health data |
| 4 | SupportWorker | Same capabilities as Carer |
| 5 | CareRecipient | Self-service; own health records |
| 6 | HealthCareProvider | Read-only access to linked CR data |
Backend¶
Controller: AuthController — /api/auth¶
| Method | Route | Auth | Description |
|---|---|---|---|
| POST | /api/auth/signup |
Anonymous | Register a new user account |
| POST | /api/auth/login |
Anonymous | Authenticate with email + password |
| POST | /api/auth/verify-mfa |
Anonymous | Verify TOTP code after primary login |
| POST | /api/auth/change-expired-password |
Anonymous | Change password when it has expired |
| POST | /api/auth/skip-mfa-setup |
Anonymous | Record that the user deferred MFA setup |
| POST | /api/auth/mfa/setup |
Authorized | Generate TOTP secret + QR code + backup codes |
| POST | /api/auth/mfa/confirm |
Authorized | Confirm MFA setup by verifying TOTP code |
| POST | /api/auth/mfa/disable |
Authorized | Disable MFA, clear secret and backup codes |
| PUT | /api/auth/profile |
Authorized | Update first/last name and/or change password |
| POST | /api/auth/forgot-password |
Anonymous | Send password-reset email |
| GET | /api/auth/validate-reset-token |
Anonymous | Validate a password-reset token |
| POST | /api/auth/reset-password |
Anonymous | Set a new password using a reset token |
Key DTOs¶
SignupRequest
email string Required
firstName string Required
lastName string Required
password string Min 8 chars, upper + lower + digit
role int 2=Admin, 3=Carer, 4=SupportWorker, 5=CareRecipient, 6=HealthCareProvider
careRecipientEmail string? Required for Carer, SupportWorker, HealthCareProvider
adminPin string? Required for Administrator role
dateOfBirth DateTime? Required for CareRecipient
gender int? CareRecipient only
conditions object[]? CareRecipient only — T1D, T2D, HBP with yearDiagnosed
initialMedication object? Optional BP medication on registration
initialDiabetesMedication object? Optional diabetes medication on registration
LoginRequest
AuthResponse
token string JWT Bearer token
requiresMfa bool True if TOTP verification step is required next
mfaEnabled bool Whether the account has MFA active
isPasswordExpired bool True if password has passed PasswordExpirationDays threshold
userId int For use in change-expired-password flow
MfaSetupResponse
secret string Base32 TOTP secret
qrCode string Base64-encoded PNG QR code image
backupCodes string[] 10 single-use recovery codes
UpdateProfileRequest
firstName string?
lastName string?
currentPassword string? Required when changing password
newPassword string? Min 8 chars, upper + lower + digit
Service: AuthService¶
Dependencies: ApplicationDbContext, IConfiguration, MfaService, CycleService, IPasswordHashingService, AppConfigService, IEmailService
LoginByEmailAsync¶
- Looks up user by email (case-insensitive).
- Verifies
PasswordHashviaIPasswordHashingService. - Checks
PasswordExpirationDaysfromAppConfigService; if elapsed sincePasswordLastChanged, setsisPasswordExpired = true— no JWT issued, returns userId for change-expired-password flow. - If user has
IsMfaEnabled = true, returnsrequiresMfa = truewith no full token; client must call/verify-mfa. - On success, calls
GenerateJwtTokenand returnsAuthResponse.
GenerateJwtToken¶
- Reads
JwtExpirationMinutesfromAppConfigService. - Fetches the default
CycleviaCycleService.GetDefaultTermAsync(). - Creates claims:
id,email,role(numeric),termId(default cycle ID),firstName,lastName. - Signs with HMAC-SHA256 using
JWT_KEYenvironment variable.
SignupAsync¶
- Validates email uniqueness (case-insensitive), password strength (min 8 chars, at least one uppercase, one lowercase, one digit).
- Creates
Userentity with hashed password. - If
careRecipientEmailprovided: looks up CR, createsUserCareRecipientlink, sends push to CR (CareRecipientLinkednotification). - If
role == Administrator: validatesadminPinagainstAdminSignupPininAppConfigService. - For CareRecipient with conditions: inserts
UserConditionrows, and optionalBpMedication/DiabetesMedicationrows. - Seeds all 12
UserNotificationPreferencerows withIsEnabled = true. - Returns JWT via
GenerateJwtToken.
EnableMfaAsync / ConfirmMfaAsync / DisableMfaAsync¶
EnableMfaAsync: Generates TOTP secret viaMfaService, generates 10 backup codes as JSON, stores temporarily (not yet saved untilConfirmMfaAsync).ConfirmMfaAsync: Verifies the submitted TOTP code, then persistsMfaSecret,BackupCodesJson, and setsIsMfaEnabled = true.DisableMfaAsync: ClearsMfaSecret,BackupCodesJson, setsIsMfaEnabled = false.
ForgotPasswordAsync / ResetPasswordAsync¶
ForgotPasswordAsync: Looks up email, generates a time-limitedPasswordResetToken, applies rate limiting (PasswordResetRequestLimitPerHourfrom AppConfig), sends HTML email viaIEmailService.ResetPasswordAsync: Validates token (not expired perPasswordResetTokenValidityMinutes), hashes new password, saves, marks token used.
Service: MfaService¶
- Generates Base32 TOTP secrets.
- Builds Google Charts QR code URL (used for authenticator app scanning).
- Verifies 6-digit TOTP codes via a TOTP library with standard 30-second window.
- Generates and validates single-use backup codes.
Model: User¶
| Field | Type | Notes |
|---|---|---|
Id |
int | PK |
Email |
string (255) | Unique, case-insensitive |
FirstName |
string | |
LastName |
string | |
PasswordHash |
string | BCrypt or PBKDF2 |
PasswordLastChanged |
DateTime? | Null = never changed |
Role |
enum (1–6) | |
IsActive |
bool | |
IsMfaEnabled |
bool | |
MfaSecret |
string? | Base32 TOTP secret |
MfaEnabledAt |
DateTime? | |
BackupCodesJson |
string? | JSON array of backup codes |
DateOfBirth |
DateTime? | CareRecipient only |
Gender |
enum? | |
CreatedAt |
DateTime | |
UpdatedAt |
DateTime |
Model: PasswordResetToken¶
| Field | Type | Notes |
|---|---|---|
Id |
int | PK |
UserId |
int | FK → User |
Token |
string | Secure random token |
ExpiresAt |
DateTime | From PasswordResetTokenValidityMinutes |
UsedAt |
DateTime? | Null = still valid |
CreatedAt |
DateTime |
Business Logic & Validation Rules¶
- Password strength: minimum 8 characters, at least one uppercase letter, one lowercase letter, one digit. Enforced on signup, reset, and profile change.
- Email uniqueness: checked case-insensitively against the
Userstable before any insert. - Password expiry:
PasswordExpirationDaysfromAppConfig. IfPasswordLastChanged(orCreatedAtif null) is more than this many days ago, login is blocked and the change-expired-password flow is triggered. - MFA bypass on biometric login: WebAuthn assertions skip the MFA challenge step entirely (handled in
WebAuthnController). - Admin PIN: required when registering with
role = Administrator. Compared againstAdminSignupPinAppConfig key. - Rate limiting on password reset: limited to
PasswordResetRequestLimitPerHourrequests per email per hour. No-reveal design — identical response returned for known and unknown emails.
Frontend¶
Routes¶
| Path | Component | Auth Required |
|---|---|---|
/login |
LoginComponent |
No |
/signup |
SignupComponent |
No |
/forgot-password |
ForgotPasswordComponent |
No |
/reset-password |
ResetPasswordComponent |
No |
/change-expired-password |
ChangeExpiredPasswordComponent |
No |
All auth routes are in AuthModule (client/src/app/auth/auth.module.ts).
LoginComponent — /login¶
File: client/src/app/auth/login.component.ts
Behaviour:
1. Displays an email + password form.
2. If the user has previously registered a biometric credential (localStorage key bgl_biometric_email matches the typed email) AND the browser has a platform authenticator available (PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()), a biometric login button appears and the password field becomes optional.
3. On standard login, calls POST /auth/login:
- If isPasswordExpired is returned → navigates to /change-expired-password?userId=….
- If requiresMfa is returned → reveals the inline MFA step with a 6-digit TOTP code input.
4. MFA step: calls POST /auth/verify-mfa with the code; on success stores the JWT via AuthService.
5. On successful login (either path), calls PushNotificationService.subscribeToServer() to register/refresh the browser push subscription.
6. "Remember Me" checkbox persists login state.
7. Redirects already-authenticated users to /dashboard on init.
Service calls:
- AuthService.login(email, password)
- AuthService.verifyMfa(code)
- BiometricService.authenticate(email) — triggers OS biometric prompt
- PushNotificationService.subscribeToServer()
SignupComponent — /signup¶
File: client/src/app/auth/signup.component.ts
Behaviour:
- Multi-section reactive form that adapts based on the selected role.
- All roles: first name, last name, email, password (with strength indicator).
- CareRecipient only: date of birth, gender, conditions (T1D, T2D, HBP, each with year diagnosed), optional initial BP medication (name, dose, frequency), optional initial diabetes medication (name, delivery route Pills|Injection, insulin delivery method Pump|Injections, pump name).
- Carer / SupportWorker / HealthCareProvider: linked care-recipient email field.
- Administrator: admin PIN field.
- Password strength enforced client-side: minimum 8 chars, uppercase, lowercase, digit.
- Calls POST /auth/register and navigates to /login on success.
ForgotPasswordComponent — /forgot-password¶
File: client/src/app/auth/forgot-password.component.ts
- Single email input field.
- Calls
POST /auth/forgot-password. - Displays the same success message regardless of whether the email exists (no-reveal design).
ResetPasswordComponent — /reset-password¶
File: client/src/app/auth/reset-password.component.ts
- Reads
?token=…from query params on init. - Validates the token via
GET /auth/validate-reset-token?token=…; shows an error message if invalid or expired. - New password input with real-time strength indicator (Weak / Medium / Strong) and a confirmation field.
- Calls
POST /auth/reset-passwordwith token + new password; navigates to/loginon success.
ChangeExpiredPasswordComponent — /change-expired-password¶
File: client/src/app/auth/change-expired-password.component.ts
- Reads
?userId=…from query params. - New password form (no current-password required; the userId acts as the credential for this one-time change).
- Calls
POST /auth/change-expired-password; on success stores the returned JWT (full login) and navigates to/dashboard.
Core Service: AuthService¶
File: client/src/app/core/services/auth.service.ts
Responsibilities:
- Stores JWT and user claims in localStorage.
- Exposes getUserRole(), getUserRoleId(), getUserId(), getEmail(), getUserName(), getTermId() helpers parsed from the JWT payload.
- hasDiabetesConditions() / hasHbpConditions() — reads a userConditions key in localStorage (populated after login) for condition-gated navigation decisions.
- isAuthenticated() — checks for a non-expired token.
- logout() — clears localStorage, navigates to /login.
- accessibleConditions$ — BehaviorSubject that emits the current user's condition list.
End-to-End Data Flow¶
Standard Login¶
Browser Angular API DB
|--- email+password --->| | |
| |--- POST /auth/login ---->| |
| | |-- query Users ---->|
| | |<-- User row -------|
| | | verify hash |
| | | check expiry |
| | | build JWT |
| |<-- AuthResponse ---------| |
| | store JWT in localStorage| |
| | subscribeToServer() | |
|<-- navigate /dashboard| | |
MFA Login¶
Browser Angular API
|--- email+password --->| |
| |--- POST /auth/login ---->|
| |<-- requiresMfa=true -----|
|<-- show TOTP input ---| |
|--- 6-digit code ------>| |
| |--- POST /auth/verify-mfa->|
| |<-- JWT token ------------|
|<-- navigate /dashboard| |
Password Reset¶
Browser Angular API Email Server
|-- enter email->| | |
| |-- POST /forgot --> | |
| | |-- send reset email ->|
| |<-- 200 OK --------| |
|<-- success msg| | |
| (user clicks link in email) | |
|-- /reset-password?token=X -------->| |
| |-- GET /validate -->| |
| |<-- 200 OK --------| |
|<-- form shown-| | |
|-- new password->| | |
| |-- POST /reset -->| |
| |<-- 200 OK --------| |
|<-- navigate /login | |