Skip to main content

Implementing FIDO2 / YubiKey in Microsoft Entra ID Without Mobile Authenticator and handling Self-Service-Password-Reset (SSPR)

·887 words·5 mins

Overview #

FIDO2 security keys offer a secure and convenient passwordless authentication method. For organizations where employees either lack company-issued phones or prefer not to use personal devices for authentication, this hardware token becomes a viable alternative. Here’s how to implement FIDO2/YubiKey as the sole authentication method in Microsoft Entra ID.

Without configuring a Self-Service Password Reset (SSPR) exception, users relying solely on a FIDO2/Yubikey will be prompted to add the Microsoft Authenticator.

A solution for this can be found in the chapter: Handling SSPR

Implementation Steps #

Enable FIDO2 Authentication

  • Navigate to the Microsoft Entra Admin Center.
  • Go to Authentication methods under the Protection section.
  • Enable Passkey (FIDO2) for your target users.

Configure Temporary Access Pass (TAP)

  • Enable TAP in Authentication Methods Settings.
  • Configure the appropriate lifetime duration (1 to 24 hours).
  • Use TAP for initial FIDO2 registration and recovery scenarios.

User Registration Process

  1. Admin generates a Temporary Access Pass (TAP) for the user.
  2. The user logs in at mysignins.microsoft.com/security-info by using the TAP.
  3. The user registers their FIDO2 security key.

Security Benefits #

  • Phishing-resistant authentication
  • No dependency on mobile devices
  • Reduced password-related IT support
  • Compatible with Windows 10/11 devices and web applications

This setup eliminates the need for Microsoft Authenticator while maintaining robust security through hardware-based authentication tokens.

Handling SSPR #

When a user relies solely on a FIDO2 Key, it is essential to exclude them from Self-Service Password Reset (SSPR) configurations.

Since only one include group can be specified for SSPR, you must create a group containing all users except those who exclusively use FIDO2 keys. For example, if you have 100 users and need to exclude 2, you must create a group of the remaining 98 users.

Password Properties

You have multiple possibilities in creating your SSPR Group:

  • Create a dynamic group, exclude any user who has a specific attribute and include everyone else. Disadvantage: For more than a handful people it’s a lot of manual work.
  • Create an Azure Automation that fills a group with users that are allowed to use SSPR:
    • Select all users excluding those that have a FIDO2 hardware token.
      Disadvantage: New users will be prompted to add the authenticator until the script runs
    • Select Users that have one or two SSPR compatible Authentication methods
      Disadvantage: Users that do not have an authentication method registered, will not get prompted to register one.

These options all require that you need to have a Conditional Access Policy that targets all users for MFA enrollment because of the following:

Enabling the “Require users to register when signing in?” option only applies to the group specified for SSPR registration.

Password reset | Registration

Azure Automation #

In this guide we will go with Option:

Select Users that have one SSPR compatible Authentication methods

Use the following Code in your Automation. (Not recommended, but you can also run this script manually) After that add the Group $groupId as SSPR-Enabled Group.

Prerequisit Modules:

  • Microsoft.Graph

Code: #

Code can also be found in our Repository: Samily-Scripts

## Variable Declartion Start ##

$clientId = "InsertClientID"
$tenantId = "InsertTenantID"
$thumbprint = "InsertThumbprint"
$groupId = "InsertGroupID" # Replace with the Object ID of your target group

# Insert Enabled Authentication Methods
$authMethodsToCheck = @("#microsoft.graph.microsoftAuthenticatorAuthenticationMethod", 
                        "#microsoft.graph.smsAuthenticationMethod", 
                        "#microsoft.graph.emailAuthenticationMethod")

#Optional UPN Filter
#$UPNs = @("xyz.com",
#          "abc.com")

## Variable Declartion End ##

Connect-MgGraph -ClientId $clientId -TenantId $tenantId -CertificateThumbprint $thumbprint -NoWelcome

# Get all users in Azure AD
$users = Get-MgUser -All -ConsistencyLevel eventual -Property Id,UserPrincipalName
$totalUsers = $users.Count # Total number of users for progress tracking
$currentUser = 0         # Counter for processed users

Write-Output "Getting Users"

# Initialize list for eligible users
$eligibleUsers = @()

Write-Output "Checking Users - Outputting Status in 50 User Increments"

# Iterate through each user and check their authentication methods
foreach ($user in $users) {
    $currentUser++

    if ($currentUser % 50 -eq 0) {
        Write-Output "Checking user $currentUser/$totalUsers"
    }

    $eligible = $true
#Optional UPN Filter
#    foreach ($UPN in $UPNs) {
#        if ($user.UserPrincipalName -like "*$UPN") {
#            $eligible = $false
#            break
#        }
#    }

    if ($eligible) {
        try {
            # Retrieve authentication methods for the user
            $authMethods = Get-MgUserAuthenticationMethod -UserId $user.Id -ErrorAction SilentlyContinue

            # Extract method types
            $authMethodTypes = $authMethods | ForEach-Object { $_.AdditionalProperties["@odata.type"] }

            # Check if user has at least one of the allowed authentication methods
            $hasAllowedAuthMethod = $false
            foreach ($method in $authMethodTypes) {
                if ($authMethodsToCheck -contains $method) {
                    $hasAllowedAuthMethod = $true
                    break
                }
            }

            # Add user to eligible list only if they have an allowed method
            if ($hasAllowedAuthMethod) {
                $eligibleUsers += $user.Id
            }
        } catch {
            Write-Output "Error processing user $($user.UserPrincipalName): $_"
            continue
        }
    }
}

Write-Output "`nFinished checking users."

# Add eligible users to the group, with progress updates
$totalEligibleUsers = $eligibleUsers.Count # Total number of eligible users
$currentEligibleUser = 0                   # Counter for added eligible users
$newMembersAdded = 0

foreach ($userId in $eligibleUsers) {
    $currentEligibleUser++

    # Update progress on the same line for adding users to the group
    Write-Output -NoNewline "`rAdding eligible user $currentEligibleUser/$totalEligibleUsers to the group"

    try {
        # Check if user is already a member of the group
        $isMember = Get-MgGroupMember -GroupId $groupId -All | Where-Object { $_.Id -eq $userId }

        if (-not $isMember) {
            New-MgGroupMember -GroupId $groupId -DirectoryObjectId $userId
            $newMembersAdded++
        }
    } catch {
        Write-Output "Could not add $userId to Group."
        continue
    }
}

Write-Output "`n`n----------------------------------------`n`n"
Write-Output "Process finished - Checked $totalUsers users - $($eligibleUsers.Count) users were eligible - Added $newMembersAdded new members to group"