@@ -34,15 +34,12 @@ public partial class AuthenticationController
3434 : AppController
3535 {
3636 private readonly AuthenticationService _authService ;
37-
3837 private readonly IUserService _userService ;
39-
4038 private readonly IMessageService _messageService ;
41-
4239 private readonly ICredentialBuilder _credentialBuilder ;
43-
4440 private readonly IContentObjectService _contentObjectService ;
4541 private readonly IMessageServiceConfiguration _messageServiceConfiguration ;
42+ private readonly IFeatureFlagService _featureFlagService ;
4643 private const string EMAIL_FORMAT_PADDING = "**********" ;
4744
4845 // Prioritize the external authentication mechanism.
@@ -57,14 +54,16 @@ public AuthenticationController(
5754 IMessageService messageService ,
5855 ICredentialBuilder credentialBuilder ,
5956 IContentObjectService contentObjectService ,
60- IMessageServiceConfiguration messageServiceConfiguration )
57+ IMessageServiceConfiguration messageServiceConfiguration ,
58+ IFeatureFlagService featureFlagService )
6159 {
6260 _authService = authService ?? throw new ArgumentNullException ( nameof ( authService ) ) ;
6361 _userService = userService ?? throw new ArgumentNullException ( nameof ( userService ) ) ;
6462 _messageService = messageService ?? throw new ArgumentNullException ( nameof ( messageService ) ) ;
6563 _credentialBuilder = credentialBuilder ?? throw new ArgumentNullException ( nameof ( credentialBuilder ) ) ;
6664 _contentObjectService = contentObjectService ?? throw new ArgumentNullException ( nameof ( contentObjectService ) ) ;
6765 _messageServiceConfiguration = messageServiceConfiguration ?? throw new ArgumentNullException ( nameof ( messageServiceConfiguration ) ) ;
66+ _featureFlagService = featureFlagService ?? throw new ArgumentNullException ( nameof ( featureFlagService ) ) ;
6867 }
6968
7069 /// <summary>
@@ -295,12 +294,13 @@ public virtual async Task<ActionResult> Register(LogOnViewModel model, string re
295294 }
296295
297296 usedMultiFactorAuthentication = result . LoginDetails ? . WasMultiFactorAuthenticated ?? false ;
297+ var enableMultiFactorAuthentication = _featureFlagService . IsNewAccount2FAEnforcementEnabled ( ) ? true : usedMultiFactorAuthentication ;
298298 user = await _authService . Register (
299299 model . Register . Username ,
300300 model . Register . EmailAddress ,
301301 result . Credential ,
302- ( result . Credential . IsExternal ( ) && string . Equals ( result . UserInfo ? . Email , model . Register . EmailAddress ) )
303- ) ;
302+ autoConfirm : ( result . Credential . IsExternal ( ) && string . Equals ( result . UserInfo ? . Email , model . Register . EmailAddress ) ) ,
303+ enableMultiFactorAuthentication : enableMultiFactorAuthentication ) ;
304304 }
305305 else
306306 {
@@ -507,6 +507,15 @@ public virtual async Task<ActionResult> LinkOrChangeExternalCredential(string re
507507 return SafeRedirect ( returnUrl ) ;
508508 }
509509
510+ // All new linking or replacing accounts should have 2FA enabled.
511+ if ( _featureFlagService . IsNewAccount2FAEnforcementEnabled ( ) && ! result . UserInfo . UsedMultiFactorAuthentication )
512+ {
513+ return ChallengeAuthentication (
514+ Url . LinkOrChangeExternalCredential ( returnUrl ) ,
515+ result . Authenticator . Name ,
516+ new AuthenticationPolicy ( ) { Email = result . LoginDetails . EmailUsed , EnforceMultiFactorAuthentication = true } ) ;
517+ }
518+
510519 var newCredential = result . Credential ;
511520 if ( await _authService . TryReplaceCredential ( user , newCredential ) )
512521 {
@@ -518,6 +527,16 @@ public virtual async Task<ActionResult> LinkOrChangeExternalCredential(string re
518527 var usedMultiFactorAuthentication = result . LoginDetails ? . WasMultiFactorAuthenticated ?? false ;
519528 await _authService . CreateSessionAsync ( OwinContext , authenticatedUser , usedMultiFactorAuthentication ) ;
520529
530+ // Update the 2FA if used during login but user does not have it set on their account.
531+ if ( result ? . LoginDetails != null
532+ && usedMultiFactorAuthentication
533+ && ! user . EnableMultiFactorAuthentication
534+ && CredentialTypes . IsExternal ( result . Credential ) )
535+ {
536+ await _userService . ChangeMultiFactorAuthentication ( user , enableMultiFactor : true , referrer : "Authentication" ) ;
537+ OwinContext . AddClaim ( NuGetClaims . EnabledMultiFactorAuthentication ) ;
538+ }
539+
521540 // Get email address of the new credential for updating success message
522541 var newEmailAddress = GetEmailAddressFromExternalLoginResult ( result , out string errorReason ) ;
523542 if ( ! string . IsNullOrEmpty ( errorReason ) )
@@ -627,6 +646,14 @@ await _authService.CreateSessionAsync(OwinContext,
627646
628647 return SafeRedirect ( returnUrl ) ;
629648 }
649+ else if ( _featureFlagService . IsNewAccount2FAEnforcementEnabled ( ) && CredentialTypes . IsExternal ( result . Credential ) && ! result . LoginDetails . WasMultiFactorAuthenticated )
650+ {
651+ // Invoke the authentication again enforcing multi-factor authentication for the same provider.
652+ return ChallengeAuthentication (
653+ Url . LinkExternalAccount ( returnUrl ) ,
654+ result . Authenticator . Name ,
655+ new AuthenticationPolicy ( ) { Email = result . LoginDetails . EmailUsed , EnforceMultiFactorAuthentication = true } ) ;
656+ }
630657 else
631658 {
632659 // Gather data for view model
@@ -703,13 +730,15 @@ internal bool ShouldEnforceMultiFactorAuthentication(AuthenticateExternalLoginRe
703730 // Enforce multi-factor authentication only if:
704731 // 1. The authenticator supports multi-factor authentication, otherwise no use.
705732 // 2. The user has enabled multi-factor authentication for their account.
706- // 3. The user authenticated with the personal microsoft account. AAD 2FA policy is controlled by the tenant admins.
707- // 4. The user did not use the multi-factor authentication for the session, obviously.
733+ // 3. The user did not use the multi-factor authentication for the session, obviously.
734+ // 4. The user authenticated with an external account (currently only MSA and AAD are supported).
735+ // 5. If the 2FA enforcement for new accounts is enabled all external account types should be enforced (step 4 validated this).
736+ // If not, only user authenticated with a personal microsoft account is enforced. AAD 2FA policy is controlled by the tenant admins.
708737 return result . Authenticator . SupportsMultiFactorAuthentication ( )
709738 && result . Authentication . User . EnableMultiFactorAuthentication
710739 && ! result . LoginDetails . WasMultiFactorAuthenticated
711740 && result . Authentication . CredentialUsed . IsExternal ( )
712- && ( CredentialTypes . IsMicrosoftAccount ( result . Authentication . CredentialUsed . Type ) ) ;
741+ && ( _featureFlagService . IsNewAccount2FAEnforcementEnabled ( ) || CredentialTypes . IsMicrosoftAccount ( result . Authentication . CredentialUsed . Type ) ) ;
713742 }
714743
715744 private string FormatEmailAddressForAssistance ( string email )
0 commit comments