From 52a47d4b33277a05d5bb850fcc1098d5e2c5ab3f Mon Sep 17 00:00:00 2001 From: George Wu Date: Sat, 21 Feb 2026 11:40:23 -0800 Subject: [PATCH] Add email_verified check for OIDC login - Parse email_verified from ID token and UserInfo endpoint - Reject OIDC login if email is present and not verified - Only applies when email is in OIDC claims (not required otherwise) --- src/application/ports/auth_ports.rs | 1 + .../services/auth_application_service.rs | 17 +++++++++++++++++ src/infrastructure/services/oidc_service.rs | 4 ++++ 3 files changed, 22 insertions(+) diff --git a/src/application/ports/auth_ports.rs b/src/application/ports/auth_ports.rs index ae24fc96..cc175e08 100644 --- a/src/application/ports/auth_ports.rs +++ b/src/application/ports/auth_ports.rs @@ -140,6 +140,7 @@ pub struct OidcTokenSet { pub struct OidcIdClaims { pub sub: String, pub email: Option, + pub email_verified: Option, pub preferred_username: Option, pub name: Option, pub groups: Vec, diff --git a/src/application/services/auth_application_service.rs b/src/application/services/auth_application_service.rs index 57b585bf..3a6dee0f 100644 --- a/src/application/services/auth_application_service.rs +++ b/src/application/services/auth_application_service.rs @@ -991,6 +991,7 @@ impl AuthApplicationService { email: user_info.email.or(claims.email), preferred_username: user_info.preferred_username.or(claims.preferred_username), name: user_info.name.or(claims.name), + email_verified: user_info.email_verified.or(claims.email_verified), groups: if user_info.groups.is_empty() { claims.groups } else { @@ -1011,6 +1012,22 @@ impl AuthApplicationService { }; let provider_name = oidc.provider_name().to_string(); + // Check email_verified - only if email is present in claims + if let Some(email) = &claims.email { + let verified = claims.email_verified.unwrap_or(false); + if !verified { + tracing::warn!( + "OIDC login rejected: email not verified (provider: {}, email: {})", + provider_name, + email + ); + return Err(DomainError::new( + ErrorKind::AccessDenied, + "OIDC", + "Email verification required. Please verify your email at the identity provider.", + )); + } + } // 4. Determine username and email let oidc_username = claims diff --git a/src/infrastructure/services/oidc_service.rs b/src/infrastructure/services/oidc_service.rs index 4e9658fe..1b0ff911 100644 --- a/src/infrastructure/services/oidc_service.rs +++ b/src/infrastructure/services/oidc_service.rs @@ -58,6 +58,7 @@ struct TokenResponse { struct IdTokenClaims { sub: String, email: Option, + email_verified: Option, preferred_username: Option, name: Option, groups: Option>, @@ -81,6 +82,7 @@ struct IdTokenClaims { struct UserInfoResponse { sub: String, email: Option, + email_verified: Option, preferred_username: Option, name: Option, groups: Option>, @@ -437,6 +439,7 @@ impl OidcServicePort for OidcService { Ok(OidcIdClaims { sub: claims.sub, email: claims.email, + email_verified: claims.email_verified, preferred_username: claims.preferred_username, name: claims.name, groups: claims.groups.unwrap_or_default(), @@ -487,6 +490,7 @@ impl OidcServicePort for OidcService { Ok(OidcIdClaims { sub: info.sub, email: info.email, + email_verified: info.email_verified, preferred_username: info.preferred_username, name: info.name, groups: info.groups.unwrap_or_default(),