Azure AD – Password Hash Synchronization – Non-Expiring Password Service Accounts

Recently I worked on implementing password hash synchronization with Azure AD Connect sync in one of the tenants I manage. This interested me on so many levels but especially the lengths that Microsoft has gone to protect this hash sync process fascinated me.

To synchronize a password, Azure AD Connect sync extracts password’s hash from on-premises AD. Extra security processing (meaning, When a user attempts to sign in to Azure AD and enters their password, the password is run through MD4+salt+PBKDF2+HMAC-SHA256 process) is applied to the password hash before it is synchronized to the Azure AD authentication service. Passwords are synchronized on a per-user basis and in chronological order.

When password hash synchronization is enabled, by default the cloud account password is set to ‘Never Expire’. This is a bit scary because if left in default state, users can still login to applications with their password that is expired in on-premise AD. Also meaning that the on-premise AD password expiration policy is not in sync with Azure AD. Users can be forced to comply with your Azure AD password expiration policy by enabling the EnforceCloudPasswordPolicyForPasswordSyncedUsers feature.

When EnforceCloudPasswordPolicyForPasswordSyncedUsers is disabled (which is the default setting), Azure AD Connect sets the PasswordPolicies attribute of synchronized users to “DisablePasswordExpiration”

Get-MsolDirSyncFeatures

To enable the EnforceCloudPasswordPolicyForPasswordSyncedUsers feature, run the following command using the MSOnline PS module

Set-MsolDirSyncFeature -Feature EnforceCloudPasswordPolicyForPasswordSyncedUsers -Enable $True

Once enabled, Azure AD does not go to each synchronized user to remove the “DisablePasswordExpiration” value from the ’PasswordPolicies’ attribute. But waits till the user’s next password change to the “DisablePasswordExpiration” from the ‘PasswordPolicies’ which is when the next password sync happens.

For this reason it is recommended to enable EnforceCloudPasswordPolicyForPasswordSyncedUsers prior to enabling password hash sync, this way the initial sync of password hashes does not add the “DisablePasswordExpiration” value to the ‘PasswordPolicies’ attribute for the users. But if you miss enabling this it is not the end of the world.

Use the below cmdlet to determine a user’s Azure AD password policy,

$user = Read-host "Enter user's UPN:"
Get-AzureADUser -objectID $user | Select DisplayName, passwordpolicies

The issue we need to address are the service accounts that live in on-premise AD with non-expiring password and their identity is synced to Azure AD so these accounts can be used in various applications. So, if you enable EnforceCloudPasswordPolicyForPasswordSyncedUsers feature and then enable password hash sync, your service accounts with non-expiring password will not have any password policy attached to it in Azure AD. These accounts will need the “DisablePasswordExpiration” policy set to them explicitly.

You can set this policy for all the non-expiring password account using the below script,

$ou1 = Get-ADUser -SearchBase 'OU=Users,OU=OU1,DC=domain,DC=com' -Filter ( passwordNeverExpires -eq $true -and enabled -eq $true } | Select userPrincipalName
$ou2 = Get-ADUser -SearchBase 'OU=Users,OU=OU2,DC=domain,DC=com' -Filter ( passwordNeverExpires -eq $true -and enabled -eq $true } | Select userPrincipalName #if you are syncing only certain OUs, this helps

$AllOu = $ou1 + $ou2

foreach ($account in $AllOU) {

            $t = (Get-AzureADUser -ObjectID $account.userPrincipalName).passwordpolicies
            if ($t -ne "DisablePasswordExpiration") {
               Set-AzureADUser -ObjectID $account.userPrincipalName -PasswordPolicies "DisablePasswordExpiration"                
    }
}

The Azure AD password policy for these account is empty when it is created in on-premise AD and the administrator creating the account can set the “DisablePasswordExpiration” policy on a per-account basis by running this below,

$user = Read-host "Enter the user's UPN"
Set-AzureADUser -ObjectID $user -PasswordPolicies "DisablePasswordExpiration"

Another caveat here is, when these account’s password is changed on-premises for whatever reason the ‘PasswordPolicies’ value switched to ‘None’

This can happen when,

  • You allow helpdesk resets service account passwords
  • You allow service account owners reset account password
  • Application admins who use these service accounts quit or change job positions and the password needs to be changed
  • Administrator creating the service account on-premise forgot to set the password policy by running the Set-AzureADUser

When the ‘PasswordPolicies’ value gets set to ‘None’ as I mentioned earlier, the account sign-ins to Azure AD will fail with error code ‘50055 — InvalidPasswordExpiredPassword — The password is expired’.

To avoid this, you can create a scheduled task on an on-premise server which run the PS script from above maybe once a week. An issue here will be for these script to run the Connect-AzureAD cmdlet needs to be run. There are probably a thousand different ways to accomplish this but for the sake of simplicity, you can consider these two options,

  • Store your credentials in plan text in the script
  • Create an encrypted, secure string password file and use it in the script

Store credentials in plain text

This is not recommended practice and never be used but there might be scenarios where you may have to use it for some quick tests. In such a scenario, you can just do something like this,

$user = "adminaccount@domain.com"
$pwd = "MySeCur3P@$$w0rd"
$secpwd = ConvertTo-SecureString $pwd -AsPlainText -Force 
$cred = New-Object System.Management.Automation.PSCredential ($user, $secpwd)
Connect-AzureAD -Credential $cred | Out-Null

All this being said, don’t use this method and if you do, please remember to delete the script after testing.

Use secure string password file

This method is far better to securely store password for automation scripts. The idea is, you create password file which has the password stored encrypted. It goes without saying that it is not a good idea to save this file as password.txt.

To create password file,

(Get-Credential).Password | ConvertFrom-SecureString | Out-File "C:\temp\sec.txt"

To silently connect to Azure AD using stored credentials,

$User = "adminaccount@domain.com"
$File = "C:\temp\sec.txt"
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, (Get-Content $File | ConvertTo-SecureString)
Connect-AzureAD -credential $credential | Out-Null

Keep in mind that you can only use this file on the computer or server where you created it. This happens because of how Windows Data Protection API encrypts the information from current computer or user account. If you try the file on a different computer you’ll get a ‘Key not valid..’ error. I think this is great and adds another layer of security.

Also, this won’t the password being decrypted or from reusing the encrypted password if it falls into wrong hands. The basic idea here is not to store password in plaintext. This method is not foolproof but good enough.

If you need a secure password file that needs to be used in multiple scripts and on different machines, AES encryption algorithm can be used and covering that will take this post way off the Azure AD non-expiring password accounts topic..too late for that..I know. 😁

Hope this helped you setup your environment before those password expired in Azure AD.

Thank you for stopping by..✌

Leave a Comment