Building an Azure Entra ID Password Reset Form with Microsoft Graph and PowerShell Universal

PowerShell PowerShell Universal Entra ID Microsoft Graph

April 17, 2025

quote Discuss this Article

In this blog post, we’ll walk through creating a password reset form for Azure Entra ID using Microsoft Graph and PowerShell Universal. We’ll use the Microsoft.Graph.Authentication and Microsoft.Graph.Users module to connect to Graph as the current user and reset another user’s password.

Azure Entra ID Configuration

The first step is to setup an Azure Entra ID app registration for PowerShell Universal. This will allow users to login using OpenID Connect and their Azure Entra ID credentials. We’ll also configure the API permissions to provide access to various parts of Microsoft Graph.

In the app registration, setup the callback path to point to the PowerShell Universal server. HTTPS is required and can be whatever port PSU is listening on. Ensure that you include a signin callback path. This will be also configured in PowerShell Universal.

Next, we ensure the following token types are selected.

Now we need to setup the permissions that we will delegate to the application and the user logging in. These permissions are required to look up users and reset their accounts. The Directory.AcccessAsUser.All permission is required to set another user’s password. It cannot be granted to an application and needs to be provided via delegation. Users in the Global Administrator group have this permission.

Finally, generate a new client secret for use in PowerShell Universal.

Configure Authentication in PowerShell Universal

Now that we have configured our app registration in Entra ID, we can configure PowerShell Universal to authenticate as the user and provide an access token to Microsoft Graph. Click Security \ Authentication and add OpenID Connect to the list of methods. In the properties, we need to configure connect the app registration to PowerShell Universal.

You’ll need to specify the following values. These are sample values and your values will be different according to your tenant:

Once you have completed configuration, you will be able to login to PowerShell Universal with OpenID Connect. To validate this, logout of your account and click the login with OpenID Connect button.

Install Microsoft Graph Modules

You will need to install the following Graph modules in order to use this example. The versions used in this example are included below.

You can do so by navigating to Platform \ Modules \ Galleries within the admin console. We used the following PowerShell Universal version for this demo.

Password Reset App

Now that we have authentication configured, we will begin to create the password reset app. First, we will need to store some variables to access Microsoft Graph via the app registration. Navigate to Platform \ Variables and create the following variables.

Once these variables have been created, navigate to Apps \ Apps and create a new app. Click the Edit Code button to define the app’s contents.

# Connect as the application. Use the content scope of process to avoid affecting other apps 
# We are passing in the tenant ID and client secret.
# We are connecting when the app is starting to avoid connecting every time the page is loaded. 
Connect-MgGraph -TenantId $PSUMgTenantId -ClientSecretCredential $Secret:PSUMgClientSecret -ContextScope Process -NoWelcome

New-UDApp -Content {

    # Create a new form with a user autocomplete and password fields. 
    New-UDForm -Children {
        New-UDAutocomplete -OnLoadOptions {
            # Use Get-MgUser to retrieve a list of available users that are filtered based on their user name. 
            # This typically takes a few seconds on the first attempt but will be faster on subsequent. 
            Get-MgUser -ConsistencyLevel eventual -Count userCount -Search "`"DisplayName:$Body`"" | ForEach-Object {
                New-UDAutocompleteOption -Name $_.DisplayName -Value $_.Id
            }
        } -Label "User" -Id 'UserId'
        New-UDTextbox -Label 'Password' -Type password -Id 'Password'
        New-UDTextbox -Label 'Confirm Password' -Type password -Id 'ConfirmPassword'
    } -OnValidate { 
        # Implement validation to ensure that the proper values are present and and passwords match
        if ([string]::IsNullOrEmpty($EventData.UserId))
        {
            New-UDValidationResult -ValidationError "User is required"
            return
        }

        if ([string]::IsNullOrEmpty($EventData.Password) -or [string]::IsNullOrEmpty($EventData.ConfirmPassword) )
        {
            New-UDValidationResult -ValidationError "Password is required"
            return
        }

        if ($EventData.Password -cne $EventData.ConfirmPassword)
        {
            New-UDValidationResult -ValidationError "Passwords do not match"
            return
        }

        # This likely isn't necessary because of the autocomplete code above. That said, it
        # provides an example of validating using the Graph cmdlets.
        $MgUser = Get-MgUser -UserId $EventData.UserId
        if ($MgUser -eq $null)
        {
            New-UDValidationResult -ValidationError "Unable to locate user."
            return
        }

        # Return a valid result if everything works well.
        New-UDValidationResult -Valid
    } -OnSubmit {

        # Ensure that we have an access token provided by Entra ID. If we don't, we can't perform this operation.
        if ($AccessToken -eq $null)
        {
            throw "Access Token is required. Ensure you have Save Tokens enabled in your OpenID Connect authentication method."
        }

        # Store the access token in the case based on the current user.
        # We do this to avoid storing the access token as plain text in the job history
        # We can also control expiration to avoid retaining it long term. We are also using the 
        # temporary cache so it will not persist restarts.
        Set-PSUCache -Key "MgAccessToken_$User" -Value $AccessToken

        # Create a random key for the password. We will clear this after resetting
        # the user's password
        $PasswordKey = (New-Guid).ToString()
        Set-PSUCache -Key $PasswordKey -Value $EventData.Password

        # We will be invoke a script with the provided information. We do it here for two reasons:
        # 1. We need to isolate the Microsoft.Graph module so the connection scope is tied only to this user
        #    The problem is that multiple user's could be accessing the PSU app at the same time. 
        # 2. By using a script, we will have an auto-history of which user reset which user's password. 
        Invoke-PSUScript -Name 'MgResetPassword.ps1' -Parameters @{
            ResetUser = $EventData.UserId
            PasswordKey = $PasswordKey
        } -Wait -Integrated | Out-Null

        # Show some feedback after the reset
        Show-UDSnackbar -Message "Password reset!" -Variant success
    }
}

The resulting app looks like this.

Reset Script

Now that we have the user interface defined, we now need to setup the reset script that will perform that actual operation. Click Automation \ Scripts and then create a new script. Name the script MgResetPassword.ps1. Click the Execution tab and set the Environment to PowerShell 7. Once created, click the Edit Code button and enter the following.

# $ResetUser is the ID of the account to reset. 
# $PasswordKey is the ID of the cached password
param($ResetUser, $PasswordKey)

# Get the password from the cache
$Password = Get-PSUCache -Key $PasswordKey
if (-not $PasswordKey)
{
    throw "Target password not found."
}

# Get the current executing user's access token from the cache. 
# We currently aren't removing this, but you could do so after the operation is complete.
# You could also set timeout values when calling Set-PSUCache in the app
$AccessToken = Get-PSUCache -Key "MgAccessToken_$($UAJob.Identity.Name)"
if (-not $AccessToken)
{
    throw "Access token not found for $AdminUser"
}

# Connect to Graph using the $AccessToken
Connect-MgGraph -AccessToken ($AccessToken | ConvertTo-SecureString -AsPlainText -Force) -NoWelcome

# Ensure that the User ID exists. 
$User = Get-MgUser -UserId $ResetUser

if ($User -eq $null)
{
    throw "Failed to look up user: $ResetUser"
}

# Create a password profile with the new password and update the user
$password = @{
    Password = $Password
    ForceChangePasswordNextSignIn = $false
}
Update-MgUser -UserId $User.Id -PasswordProfile $password

# Remove the password from the cache
Remove-PSUCache -Key $PasswordKey

With the script created, try to use your app. You will need to login as a user with the proper Entra ID permissions to perform these operations. After clicking submit on the form, you will see a successful notification and a new job will be listed in the job history about the operation.