Skip to content

Register Yubikeys on behalf of your users with Microsoft Entra ID FIDO2 provisioning APIs

Microsoft recently announced their new FIDO2 provisioning APIs within Microsoft Entra ID. While users can register their FIDO2 keys fairly easily with a Temporary Access Pass, the new API allows admins to register keys on behalf of a user. This can be extremely handy in onboarding scenarios or in case a new key needs to be shipped to a vendor or contract worker.

The Microsoft APIs support every vendor of FIDO2 (passkeys), but Yubico has made some extra effort to provide a sample Python script that uses the Yubikey Manager under the hood. For the folks scared to touch Python (I feel ya), please go to the end of the blog post for PowerShell sample code (creds), but don’t skip the theory. It’s essential to understand the process.

How does this work?

The provisioning of security keys happens in three steps.

Source: Microsoft strengthens phishing-resistant security for Entra ID with FIDO2 provisioning APIs | Yubico

First, we ask for the creation options from Graph API. This will give us all the required information to create the credentials on the Yubikey. The script will provide this step, but if we run it using Graph Explorer, we can get a sense of what it looks like.

The next step is the ‘local’ part, where we need to add that credential to the Yubikey, using the information from the relying party (Microsoft). For this, we use a sample script that Yubico provided. This script will take care of the magic and talks to the Yubikey directly to create the new PIN and a new cryptographic keypair. This is done using the CTAP protocol.

After this step, we can finally register the key to Entra ID, using Graph API.

I expected this to be very hard to do or understand, but I got this up and running in a few minutes. Let’s take a look together, shall we?

What do we need?

Now, I assume you can install the software requirements without guidance, but in case you need help with the Python modules, here are the commands:

pip install yubikey-manager
pip install fido2

I recommend running the script or Visual Studio terminal as an administrator.

Yubico sample scripts

Download the ZIP file from GitHub and put it somewhere on your local drive. Open the folder with Visual Studio Code and open the configs.json file. This is where we must put in our credentials, the group, and other parameters. Let’s take care of the credentials first.

Prepare Entra ID

First, let’s create a new app registration in Entra ID, and add the following permissions:

AuthenticationMethod.ReadWrite.All to create (and delete) the FIDO keys.
GroupMember.Read.All to read the provisioning group.

We then create a new secret and copy the value into the configs.json file, along with the app registration’s client ID and the tenant’s name.

The group can be a new or existing group with your test user(s).

Needless to say, the users must also be enabled for passkeys in Entra ID. In this demo, it is enabled for all users. Please check your current config.

I’ve also configured the other parameters to create a random PIN, and to delete any existing FIDO keys that exist for my user(s).

Here’s what my config file looks like:

{
    "tenantName": "M365x341716.onmicrosoft.com",
    "client_id": "e5d678ad-36a4-428a-8f4f-c8088401ca37",
    "client_secret": "****************************************",
    "usersInScopeGroup": "FIDO Provisioning",
    "challengeTimeoutInMinutes": 60,
    "deleteExistingUserFIDOCredentials": true,        
    "useRandomPIN": true,
    "useCTAP21Features": false
}

Let’s go!

Cool! That was the hardest part. Now, we can see if the Python script runs. You might encounter a few missing modules, so you might want to do some trial and error.

Let’s quickly explain what the script does:

Step 1: Start with step1GetFIDO2Challenges.py. This will request a token from Entra ID using the app registration and then read the group members.

It will then delete any existing keys (if you set the deleteExistingUserFIDOCredentials parameter to ‘true’) for the user, and request the new creation options. This information will be added to the usersToRegister.csv file.

Step 2: The next and final step is to run step2CreateAndActivateCredential.py. This will read the lines from the usersToRegister.csv file and create a new credential for the key. This is where you input the Yubikey.

The information will then be stored in the keysRegistered.csv file. If you set the useRandomPIN parameter to ‘true’, the PIN will also be provided here.

The passkey has been added to the account and is also visible in Entra ID.

I’ve also created a quick video to explain the steps, so I hope that helps!

CTAP2.1 features

As you might have spotted, the script also provides a CTAP2.1 flag. When we set this to ‘true,’ we can enforce a specific PIN length and create a provisional PIN for our users.

After reading this marvelous blog post, I ordered a Yubikey BIO to test the script, and it works like a charm.

The first time the FIDO key is used with the temporary PIN, the PIN needs to be changed.

PowerShell all the things

Now, as promised, I also will share a PowerShell sample script. It will do the job for one key, but it cannot handle bulk requests, provisional PIN etc. If you find a better one, please let me know so I can share it here.

#Install the DSInternals module
Install-Module -Name DSInternals.Passkeys

#Connect tot Microsoft Graph.
Connect-MgGraph -Scopes UserAuthenticationMethod.ReadWrite.All -TenantId <REPLACE>.onmicrosoft.com -NoWelcome

#Prep the variables 
$UPN = "<REPLACE>"
$DisplayName = "<REPLACE>"

#Get the FIDO registration options from Graph API
$RegistrationOptions = Get-PasskeyRegistrationOptions -UserId $UPN

#Create new credential on the security key (using CTAP)
$Passkey = Get-PasskeyRegistrationOptions -UserId $UPN | New-Passkey -DisplayName $DisplayName

#Prep the variables for Entra ID

$JSON = $Passkey | ConvertFrom-Json
$Attestationobject = $JSON.publicKeyCredential.response.attestationObject
$ClientDataJson = $JSON.publicKeyCredential.response.clientDataJSON
$id = $JSON.publicKeyCredential.id

#Prep the request for Graph API

$Body = @"
{
  "displayName": "$DisplayName",
  "publicKeyCredential": {
    "id": "$ID",
    "response": {
      "clientDataJSON": "$clientDataJSON",
      "attestationObject": "$Attestationobject"
    }
  }
}
"@

#Register the key in Entra ID

$URI = "https://graph.microsoft.com/beta/users/$UPN/authentication/fido2Methods"
[string]$response = Invoke-MgGraphRequest -Method "POST" -Uri $URI -OutputType "Json" -ContentType 'application/json' -Body $Body

PowerShell GUI by Token2

Update 29-11-2024. I also wanted to share this PowerShell tool, created by Token2. token2/fido2_bulkenroll_entraid: FIDO2 Key Automated Registration for Entra ID – PowerShell Solution

Let’s wrap up

I hope this post was valuable to you.
I’m happy to see that Yubico provided this sample code, as it will help me better understand the possibilities.

With some small adjustments, these scripts can be used with any FIDO2 key. If you’re a developer, I’m sure you can even build a nice front-end for this!

Here are the resources that I used:

Microsoft strengthens phishing-resistant security for Entra ID with FIDO2 provisioning APIs | Yubico
Public preview: Microsoft Entra ID FIDO2 provisioning APIs – Microsoft Community Hub
YubicoLabs/entraId-register-passkeys-on-behalf-of-users: Sample code using Microsoft Graph APIs to register FIDO2 security keys for Entra ID users (github.com)
Register FIDO2 Passkey in Entra ID on behalf of users with PowerShell – Icewolf Blog

Stay safe!

2 thoughts on “Register Yubikeys on behalf of your users with Microsoft Entra ID FIDO2 provisioning APIs”

  1. That is great! Can you do the same with passkeys in the authenticator app? We add the app via Intune to all our endusers, but passkey registration is tricky for our non-techsavvy users. Would be great if we could hand out phones with registered passkeys directly.

  2. From where do you execute the commands for the modules?
    pip install yubikey-manager
    pip install fido2
    Can you explain that step – thanks.

Leave a Reply

Your email address will not be published. Required fields are marked *