{"record":{"id":"fa6d785265592804","repo":"fullstackhero/dotnet-starter-kit","slug":"email-not-confirmed","errorCode":null,"errorMessage":"email not confirmed","messagePattern":"email not confirmed","errorType":"exception","errorClass":"UnauthorizedException","httpStatus":401,"severity":"error","filePath":"src/Modules/Identity/Modules.Identity/Services/IdentityService.cs","lineNumber":273,"sourceCode":"        if (user.RefreshTokenExpiryTime <= now)\n        {\n            _logger.LogWarning(\n                \"Refresh token expired for user {UserId}. Expired at: {ExpiryTime}, Current time: {CurrentTime}\",\n                user.Id, user.RefreshTokenExpiryTime, now);\n            throw new UnauthorizedException(\"refresh token is invalid or expired\");\n        }\n    }\n\n    private static void ValidateUserStatus(FshUser user)\n    {\n        if (!user.IsActive)\n        {\n            throw new UnauthorizedException(\"user is deactivated\");\n        }\n\n        if (!user.EmailConfirmed)\n        {\n            throw new UnauthorizedException(\"email not confirmed\");\n        }\n    }\n\n    private void ValidateTenantStatus(AppTenantInfo tenant)\n    {\n        if (tenant.Id == MultitenancyConstants.Root.Id)\n        {\n            return;\n        }\n\n        if (!tenant.IsActive)\n        {\n            throw new UnauthorizedException($\"tenant {tenant.Id} is deactivated\");\n        }\n\n        // Honor the billing grace period: a lapsed tenant can still authenticate until\n        // ValidUpto + grace (matching the request-time guard in MultitenancyModule).\n        if (_timeProvider.GetUtcNow().UtcDateTime > tenant.ValidUpto.AddDays(_gracePeriodDays))","sourceCodeStart":255,"sourceCodeEnd":291,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Identity/Modules.Identity/Services/IdentityService.cs#L255-L291","documentation":"ValidateUserStatus throws UnauthorizedException(\"email not confirmed\") when the user account exists and is active but EmailConfirmed is false. ASP.NET Identity's confirmation flag gates token issuance here, so unconfirmed accounts cannot log in or refresh.","triggerScenarios":"Login/refresh for a user who signed up but never clicked the confirmation link; SMTP delivery failed so the confirmation email never arrived; a user created by an admin/seed without EmailConfirmed=true.","commonSituations":"Misconfigured email provider (SendGrid/Mailgun key invalid) silently dropping confirmation mails; dev environments without email infrastructure; tests seeding users with EmailConfirmed=false; user's mail client spam-filtering the message.","solutions":["Confirm the email: resend the confirmation link (SendConfirmEmail/forgot-password style flow) and complete it.","Manually set EmailConfirmed=true for trusted/dev accounts (admin update or DB).","Fix SMTP/email provider configuration and check logs if confirmation mails are not being delivered."],"exampleFix":"// before (seed user cannot log in)\nnew FshUser { Email = \"test@root.dev\", EmailConfirmed = false }\n// after\nnew FshUser { Email = \"test@root.dev\", EmailConfirmed = true }","handlingStrategy":"try-catch","validationCode":"// app state knows whether the signup flow finished confirmation\nif (signupState.awaitingEmailConfirmation) {\n  showResendConfirmationUi();\n  return; // skip login attempt\n}","typeGuard":null,"tryCatchPattern":"catch (ApiError e) when (e.StatusCode === 401 && e.Message.includes('email not confirmed')) {\n  await api.post('/api/users/self/confirm-email/resend');\n  show('Check your inbox to confirm your email, then sign in.');\n}","preventionTips":["Verify SMTP/provider configuration in every environment; alert on delivery failures.","In signup UX, gate on the confirmation step before attempting login.","Seed/test users with EmailConfirmed=true in non-production."],"tags":["auth","email-confirmation","http-401"],"backgroundTag":"authentication-required","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}