fullstackhero/dotnet-starter-kit · error · UnauthorizedException
email not confirmed
Error message
email not confirmed
What it means
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.
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.
Example fix
// before (seed user cannot log in)
new FshUser { Email = "test@root.dev", EmailConfirmed = false }
// after
new FshUser { Email = "test@root.dev", EmailConfirmed = true } Defensive patterns
Strategy: try-catch
Validate before calling
// app state knows whether the signup flow finished confirmation
if (signupState.awaitingEmailConfirmation) {
showResendConfirmationUi();
return; // skip login attempt
} Try / catch
catch (ApiError e) when (e.StatusCode === 401 && e.Message.includes('email not confirmed')) {
await api.post('/api/users/self/confirm-email/resend');
show('Check your inbox to confirm your email, then sign in.');
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Unauthorized
- refresh token is invalid or expired
- user is deactivated
- tenant is deactivated
- no current user
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/fa6d785265592804.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Identity/Modules.Identity/Services/IdentityService.cs:273
if (user.RefreshTokenExpiryTime <= now)
{
_logger.LogWarning(
"Refresh token expired for user {UserId}. Expired at: {ExpiryTime}, Current time: {CurrentTime}",
user.Id, user.RefreshTokenExpiryTime, now);
throw new UnauthorizedException("refresh token is invalid or expired");
}
}
private static void ValidateUserStatus(FshUser user)
{
if (!user.IsActive)
{
throw new UnauthorizedException("user is deactivated");
}
if (!user.EmailConfirmed)
{
throw new UnauthorizedException("email not confirmed");
}
}
private void ValidateTenantStatus(AppTenantInfo tenant)
{
if (tenant.Id == MultitenancyConstants.Root.Id)
{
return;
}
if (!tenant.IsActive)
{
throw new UnauthorizedException($"tenant {tenant.Id} is deactivated");
}
// Honor the billing grace period: a lapsed tenant can still authenticate until
// ValidUpto + grace (matching the request-time guard in MultitenancyModule).
if (_timeProvider.GetUtcNow().UtcDateTime > tenant.ValidUpto.AddDays(_gracePeriodDays))View on GitHub (pinned to 3f2959e683)