Updating an expired Client Secret of a SharePoint Add-in using Azure-AD/Az Cli


Back in May 2016, I wrote a post to show you how to update the Client Secret of a SharePoint add-in. This used the PowerShell module MSOL. https://cann0nf0dder.wordpress.com/2016/05/18/updating-an-expired-client-secret-of-sharepoint-add-in/

The MSOL module is now (or going to be) deprecated. Therefore, I needed to find a different way of doing this, and ideally something that could be done with Azure Dev-ops pipelines. I originally started with AZ CLI. Although the Client Secret is tied to a Service Principal in Azure AD, I was unable to change the Key Credentials or Password Credentials for it.

Due to the issues I was getting with AZ CLI, I used Azure-AD module instead. See at end of blog post, how I got Az CLI to work with a workaround.

Updating Client Secret

Connect to Azure-AD

First you will need to connect to Azure-AD

#Install AzureAD
Write-Information -MessageData:"Getting if the AzureAD powershell module is available..."
if(-not (Get-Module AzureAD)) {
Write-Information -MessageData:"Installing the NuGet Package provider..."
Install-PackageProvider -Name:NuGet -Force -Scope:CurrentUser
Write-Information -MessageData:"Installing the AzureAD Powershell Module..."
Install-Module AzureAD -Scope:CurrentUser -Force
}
$Credential = Get-Credential
Connect-AzureAD -Credential $Credential

The above code ensures that you have Azure AD installed on your machine, and logs you in.

Getting the Add-in as a Service Principal

Once you have logged in, you will be able to call back your SharePoint Add-in. The SharePoint Add-in is actually a Service Principal within Azure AD, and we will grab this using Get-AzureADServicePrincipal. You can do this by using the AppId, or the AppName. The AppId is fine to use, but when you want to use the same script across multiple environments, you will need to ensure you are passing in the different AppId for each environment. THis is why I have used the Name of the Add-In. (Assuming you have given your App the same name in each environment)

$serviceprincipal = Get-AzureADServicePrincipal -All:$true -Filter "DisplayName eq 'Demo App'"
#OR If using APP ID.
$serviceprincipalByID = Get-AzureADServicePrincipal -All:$true -Filter "AppId eq 'ab739749-827d-4437-90e5-bf181c5407e0'"

Create a new Secret

Next you need to be able to create a new secret. This is done by creating random bytes and converting to a Base64String. I ensure the password is valid for an additional 2 years.

$bytes = New-Object Byte[] 32
$rand = [System.Security.Cryptography.RandomNumberGenerator]::Create()
$rand.GetBytes($bytes)
$rand.Dispose()
$newClientSecret = [System.Convert]::ToBase64String($bytes)
$dtStart = [System.DateTime]::Now
$dtEnd = $dtStart.AddYears(2)
write-output $newClientSecret

Create Key Credentials and Password Credential for the App

The SharePoint Add-In requires 2 Key Credentials (One Sign and one Verify) and 1 Password Credentials. The following script creates new ones, this allows both the old password and the new password to continue working at the same time, until you are able to update the code that uses the ClientID and ClientSecret.

Write-Information "Updating KeyCredential Usage Sign..."
New-AzureADServicePrincipalKeyCredential -ObjectId $serviceprincipal.ObjectId -Type:Symmetric -Usage:Sign -Value $newClientSecret -StartDate $dtStart -EndDate $dtEnd | Out-Null
Write-Information "Updating KeyCredential Usage Verify..."
New-AzureADServicePrincipalKeyCredential -ObjectId $serviceprincipal.ObjectId -Type:Symmetric -Usage:Verify -Value $newClientSecret -StartDate $dtStart -EndDate $dtEnd | Out-Null
Write-Information "Updating PasswordCredential..."
New-AzureADServicePrincipalPasswordCredential -ObjectId $serviceprincipal.ObjectId -Value $newClientSecret -StartDate $dtStart -EndDate $dtEnd | Out-Null

Removing the original Key and Password Credential for the App

The following code shows how to loop round the Key and Password credentials and remove the original ones. It does this by looking for any Credentials that were created before the start date of the new ones. I would only run this part of my code, once I know I have updated my application to use the new password. Not in my example here, but where I’m using it in the real world, my Client Secret is stored within a Keyvault. I would update the keyvault value (ensuring to disable the previous version).

Write-Information "Remove all KeyCredential started before $(Get-Date $dtStart -Format 'O' )..."
$serviceprincipal = Get-AzureADServicePrincipal -All:$true -Filter "DisplayName eq '$SharePointAddInName'"
$serviceprincipal.KeyCredentials | ForEach-Object{
$credential = $PSItem
if($($credential.StartDate) -lt $dtStart)
{
Write-Information -MessageData:"Removing KeyCredential $($credential.KeyId)"
Remove-AzureADServicePrincipalKeyCredential -ObjectId:$serviceprincipal.ObjectId -KeyId:$credential.KeyId
}
}
Write-Information "Remove all PasswordCredential started before $(Get-Date $dtStart -Format 'O' )..."
$serviceprincipal.PasswordCredentials | ForEach-Object{
$credential = $PSItem
if($($credential.StartDate) -lt $dtStart)
{
Write-Information -MessageData:"Removing PasswordCredential $($credential.KeyId)"
Remove-AzureADServicePrincipalPasswordCredential -ObjectId:$serviceprincipal.ObjectId -KeyId:$credential.KeyId
}
}

Connecting with AZ Cli Workaround

Using Azure Dev-Ops Pipeline, I really wanted to use AZ cli to be able to update the Client Secret of a SharePoint Add-in. Due to the error messages when I attempted to update the Service Principal Key Credential and Password Credentials, I was forced to use Azure-AD instead. So how can I uses AZ Cli.

My Dev-Ops Pipeline uses a service account, and I have ensured this service account has permissions to update the directory.

Then I can connect from Az Cli to Azure-AD doing the following:

#Once signed into Azure CLI
$Token = az account get-access-token --resource-type "aad-graph" | ConvertFrom-Json
$AzAccount = az account show | ConvertFrom-Json
Connect-AzureAD -AadAccessToken $($Token.accessToken) -AccountId:$($AzAccount.User.Name) -TenantId:$($AZAccount.tenantId)

I add the above code in the full script just after the parameter, as the pipeline will already be signed in as the Pipeline Service Principal. It will then grab the Access token to sign in with Azure AD, and then able to run the rest of the script.

Full Script

<#
.SYNOPSIS
Updates the SharePoint Add-in Secret everytime.
It expects that you are already connected to Azure AD
.EXAMPLE
.\Update-SharePointAddIn.ps1 -SharePointAddInName "Demo App"
#>
param(
[Parameter(Manadatory)]
[string]
$SharePointAddInName
)
$ErrorActionPreference = 'Stop'
$InformationPreference = 'Continue'
#Call AzCliToAzureAD.ps1 here for Pipeline.
#Create Pasword
$bytes = New-Object Byte[] 32
$rand = [System.Security.Cryptography.RandomNumberGenerator]::Create()
$rand.GetBytes($bytes)
$rand.Dispose()
$newClientSecret = [System.Convert]::ToBase64String($bytes)
$dtStart = [System.DateTime]::Now
$dtEnd = $dtStart.AddYears(2)
Write-Information "Getting service principal named: $SharePointAddInName..."
$serviceprincipal = Get-AzureADServicePrincipal -All:$true -Filter "DisplayName eq '$SharePointAddInName'"
if($null -eq $serviceprincipal)
{
Write-Error "Unable to find service principal named: $SharePointAddInName"
}
Write-Information "Updating KeyCredential Usage Sign..."
New-AzureADServicePrincipalKeyCredential -ObjectId $serviceprincipal.ObjectId -Type:Symmetric -Usage:Sign -Value $newClientSecret -StartDate $dtStart -EndDate $dtEnd | Out-Null
Write-Information "Updating KeyCredential Usage Verify..."
New-AzureADServicePrincipalKeyCredential -ObjectId $serviceprincipal.ObjectId -Type:Symmetric -Usage:Verify -Value $newClientSecret -StartDate $dtStart -EndDate $dtEnd | Out-Null
Write-Information "Updating PasswordCredential..."
New-AzureADServicePrincipalPasswordCredential -ObjectId $serviceprincipal.ObjectId -Value $newClientSecret -StartDate $dtStart -EndDate $dtEnd | Out-Null
#Update the application here.
#For example add the secret to a key vault that the application is getting the secret from.
Write-Information "Remove all KeyCredential started before $(Get-Date $dtStart -Format 'O' )..."
$serviceprincipal = Get-AzureADServicePrincipal -All:$true -Filter "DisplayName eq '$SharePointAddInName'"
$serviceprincipal.KeyCredentials | ForEach-Object{
$credential = $PSItem
if($($credential.StartDate) -lt $dtStart)
{
Write-Information -MessageData:"Removing KeyCredential $($credential.KeyId)"
Remove-AzureADServicePrincipalKeyCredential -ObjectId:$serviceprincipal.ObjectId -KeyId:$credential.KeyId
}
}
Write-Information "Remove all PasswordCredential started before $(Get-Date $dtStart -Format 'O' )..."
$serviceprincipal.PasswordCredentials | ForEach-Object{
$credential = $PSItem
if($($credential.StartDate) -lt $dtStart)
{
Write-Information -MessageData:"Removing PasswordCredential $($credential.KeyId)"
Remove-AzureADServicePrincipalPasswordCredential -ObjectId:$serviceprincipal.ObjectId -KeyId:$credential.KeyId
}
}