User Authentication Flow - ExpandedUser Authentication Flow - ExpandedService LayerInfrastructure LayerRepository LayerAPIAPIRegisterServiceLoginServiceTokenServiceTokenServiceTokenServiceTokenServiceEmailServiceArgon2Argon2JWTJWTJWTJWTEmailTemplateAuthRepositoryAuthRepositoryAuthRepositoryAuthRepositoryAuthRepositorySQL ServerSQL ServerSQL ServerSQL ServerSQL ServerUserAPIRegisterServiceLoginServiceTokenServiceEmailServiceArgon2JWTEmailTemplateAuthRepositoryUserAccountSQL ServerUserUserAPIControllerAPIControllerRegisterServiceRegisterServiceLoginServiceLoginServiceTokenServiceTokenServiceEmailServiceEmailServiceArgon2InfrastructureArgon2InfrastructureJWTInfrastructureJWTInfrastructureEmailProviderEmailProviderTemplateProviderTemplateProviderAuthRepositoryAuthRepositoryUserAccountRepositoryUserAccountRepositorySQL ServerStored ProceduresSQL ServerStored ProceduresAPIAPIRegisterServiceLoginServiceTokenServiceTokenServiceTokenServiceTokenServiceEmailServiceArgon2Argon2JWTJWTJWTJWTEmailTemplateAuthRepositoryAuthRepositoryAuthRepositoryAuthRepositoryAuthRepositorySQL ServerSQL ServerSQL ServerSQL ServerSQL ServerRegistration FlowPOST /api/auth/register{username, firstName, lastName,email, dateOfBirth, password}FluentValidation runs:- Username: 3-64 chars, alphanumeric + [._-]- Email: valid format, max 128 chars- Password: min 8 chars, uppercase,\n lowercase, number, special char- DateOfBirth: must be 19+ years oldValidate request(FluentValidation)alt[Validation fails]400 Bad Request{errors: {...}}[Validation succeeds]RegisterAsync(userAccount,password)GetUserByUsernameAsync(username)EXECusp_GetUserAccountByUsernamenull (user doesn't exist)GetUserByEmailAsync(email)EXECusp_GetUserAccountByEmailnull (email doesn't exist)alt[User/Email already exists]throw ConflictException409 Conflict"Username or email alreadyexists"[User doesn't exist]Hash(password)Argon2id parameters:- Salt: 16 bytes (128-bit)- Memory: 64MB- Iterations: 4- Parallelism: CPU count- Hash output: 32 bytesGenerate random salt(16 bytes)Hash password withArgon2id algorithm"base64(salt):base64(hash)"RegisterUserAsync( username, firstName, lastName, email, dateOfBirth, hash)EXEC USP_RegisterUserTransaction begins:1. INSERT UserAccount2. INSERT UserCredential(with hashed password)Transaction commitsBEGIN TRANSACTIONINSERT INTO UserAccount(Username, FirstName,LastName,Email, DateOfBirth)OUTPUTINSERTED.UserAccountIDINSERT INTO UserCredential(UserAccountId, Hash)COMMIT TRANSACTIONUserAccountId (GUID)UserAccount entityGenerateAccessToken(userAccount)GenerateJwt(userId, username,expiry)JWT Configuration:- Algorithm: HS256- Expires: 1 hour- Claims:* sub: userId* unique_name: username* jti: unique token IDCreate JWT with claimsSign with secret keyAccess TokenAccess TokenGenerateRefreshToken(userAccount)GenerateJwt(userId, username,expiry)Refresh Token:- Expires: 21 days- Same structure as access tokenRefresh TokenRefresh TokenSendRegistrationEmailAsync( createdUser, confirmationToken)RenderUserRegisteredEmailAsync( firstName, confirmationLink)Razor Component:- Header with branding- Welcome message- Confirmation button- FooterRender Razor componentto HTMLHTML email contentSendAsync(email, subject, body)SMTP Configuration:- Host: from env (SMTP_HOST)- Port: from env (SMTP_PORT)- TLS: StartTLS- Auth: username/passwordCreate MIME messageConnect to SMTP serverAuthenticateSend emailDisconnectSuccess / Failurealt[Email sent successfully]emailSent = true[Email failed]emailSent = false(error suppressed)RegisterServiceReturn( userAccount, accessToken, refreshToken, emailSent)Create response body201 Created{ message: "User registeredsuccessfully", payload: { userAccountId, username, accessToken, refreshToken, confirmationEmailSent }}Login FlowPOST /api/auth/login{username, password}Validate request(FluentValidation)alt[Validation fails]400 Bad Request{errors: {...}}[Validation succeeds]LoginAsync(username, password)GetUserByUsernameAsync(username)EXECusp_GetUserAccountByUsernameSELECT FROM UserAccountWHERE Username = @UsernameUserAccount entityalt[User not found]throw UnauthorizedException"Invalid username or password"401 Unauthorized[User found]GetActiveCredentialByUserAccountIdAsync(userId)EXECUSP_GetActiveUserCredentialByUserAccountIdSELECT FROM UserCredentialWHERE UserAccountId = @UserAccountIdAND IsRevoked = 0UserCredential entityalt[No active credential]throw UnauthorizedException401 Unauthorized[Active credential found]Verify(password, storedHash)1. Split stored hash: "salt:hash"2. Extract salt3. Hash provided password\n with same salt4. Constant-time comparisonParse salt from stored hashHash provided passwordwith extracted saltFixedTimeEquals( computed, stored)true/falsealt[Password invalid]throw UnauthorizedException401 Unauthorized[Password valid]GenerateAccessToken(user)GenerateJwt(...)Access TokenAccess TokenGenerateRefreshToken(user)GenerateJwt(...)Refresh TokenRefresh TokenLoginServiceReturn( userAccount, accessToken, refreshToken)200 OK{ message: "Logged insuccessfully", payload: { userAccountId, username, accessToken, refreshToken }}Error Handling (Global Exception Filter)GlobalExceptionFilter catches:- ValidationException → 400 Bad Request- ConflictException → 409 Conflict- NotFoundException → 404 Not Found- UnauthorizedException → 401 Unauthorized- ForbiddenException → 403 Forbidden- All others → 500 Internal Server Error