Connect to delegated Office 365 tenants via PowerShell using the Secure App Model

Consent to delegated admin partner application for the secure app model

To increase security, Microsoft is requiring that Microsoft Partners have Multi-Factor authentication enabled on all partner accounts.

To ensure that delegated admin scripts continue to work correctly, Microsoft Partners will need to authenticate using the secure app model.

This guide will cover the 3 steps to set this up.

1. Create a new Azure AD Application with access to Partner Center API

The secure app model requires the setup of a new Azure AD application with permission to access the Partner Center API.

2. Retrieve a refresh token using your delegated admin account and the new Azure AD Application

Once the new application is set up, we need to use the Partner Center PowerShell module to authenticate using this application and to retrieve a refresh token.

3. Modify your scripts to use the refresh token to retrieve access tokens to connect to tenants via PowerShell

Once we’ve retrieved the Refresh Token, we can use it in our scripts in place of credential objects. Since the refresh token is all you need to authenticate, it’s very important that it’s stored and accessed securely. Microsoft recommends the use of Azure Key Vault for storing and retrieving the refresh token value.

In Microsoft’s examples, the Partner Center PowerShell module is used to retrieve access tokens using your refresh token. Since we don’t want to install or import the Partner Center module on all of our Azure Functions/Azure Web Jobs/other schedulers, we are using our own PowerShell function to retrieve the access tokens.

Note: Since the refresh token, client ID and secret are a replacement for your credential objects, it’s very important that they are secured appropriately. Microsoft recommends using Azure Key Vault to secure these values.

Prerequisites

  • To set up the new Azure AD Application, you’ll need to be a global admin in a Microsoft Partner tenant.
  • To retrieve the initial refresh token for use in your scripts, you’ll need to use the Partner Center PowerShell module. You can install this by opening PowerShell as an administrator and running:
    Install-Module PartnerCenter
  • For testing, you’ll also need the MSOnline Module:
    Install-Module MSOnline
  • And the AzureAD Module:
    Install-Module AzureAD

Step 1: Create new Azure AD Application with access to Partner Center API

This script is a slightly modified version of the one found here. It will create a new Azure AD Application in your organisation with access to the Partner Center API, your own Azure AD, and is pre-consented to access your customers’ environments.

How to run this PowerShell script

    1. Double click the below script to select it
    2. Copy and paste it into a new file in Visual Studio code and save it with a .ps1 extension.
    3. Install the recommended PowerShell Extension if you haven’t already
    4. Press F5 to run the script
    5. Sign into Azure AD with the credentials of a global admin in your own tenant.
    6. Wait for the script to complete.
    7. The app/client ID and secret will appear in the console, as well as in an exported CSV at C:\temp\azureadapps.csv. Once you have retrieved these details, be sure to delete the CSV.
    8. If you’re running your scripts using an account that isn’t a global admin in your own tenant, you will need to provide consent for other users to use this application in the Azure Portal here.Azure Active Directory Application Registrations
    9. Find your application under All applications, click API permissions, then click the Grant admin consent buttonGrant Consent To Azure AD Application

Script to create Azure AD Application with access to Partner Center API for Secure App Model

$DisplayName = "Partner Center Application"
$ConfigurePreconsent = $true

$ErrorActionPreference = "Stop"

# Check if the Azure AD PowerShell module has already been loaded.
if ( ! ( Get-Module AzureAD ) ) {
    # Check if the Azure AD PowerShell module is installed.
    if ( Get-Module -ListAvailable -Name AzureAD ) {
        # The Azure AD PowerShell module is not load and it is installed. This module
        # must be loaded for other operations performed by this script.
        Write-Host -ForegroundColor Green "Loading the Azure AD PowerShell module..."
        Import-Module AzureAD
    }
    else {
        Install-Module AzureAD
    }
}

try {
    Write-Host -ForegroundColor Green "When prompted please enter the appropriate credentials..."

    if ([string]::IsNullOrEmpty($TenantId)) {
        Connect-AzureAD | Out-Null
        $TenantName = (Get-AzureADTenantDetail).DisplayName
        $TenantId = (Get-AzureADTenantDetail).ObjectId
    }
    else {
        Connect-AzureAD -TenantId $TenantId | Out-Null
    }
}
catch [Microsoft.Azure.Common.Authentication.AadAuthenticationCanceledException] {
    # The authentication attempt was canceled by the end-user. Execution of the script should be halted.
    Write-Host -ForegroundColor Yellow "The authentication attempt was canceled. Execution of the script will be halted..."
    Exit
}
catch {
    # An unexpected error has occurred. The end-user should be notified so that the appropriate action can be taken.
    Write-Error "An unexpected error has occurred. Please review the following error message and try again." `
        "$($Error[0].Exception)"
}

$adAppAccess = [Microsoft.Open.AzureAD.Model.RequiredResourceAccess]@{
    ResourceAppId  = "00000002-0000-0000-c000-000000000000";
    ResourceAccess =
    [Microsoft.Open.AzureAD.Model.ResourceAccess]@{
        Id   = "5778995a-e1bf-45b8-affa-663a9f3f4d04";
        Type = "Role"
    },
    [Microsoft.Open.AzureAD.Model.ResourceAccess]@{
        Id   = "a42657d6-7f20-40e3-b6f0-cee03008a62a";
        Type = "Scope"
    },
    [Microsoft.Open.AzureAD.Model.ResourceAccess]@{
        Id   = "311a71cc-e848-46a1-bdf8-97ff7156d8e6";
        Type = "Scope"
    }
}

$graphAppAccess = [Microsoft.Open.AzureAD.Model.RequiredResourceAccess]@{
    ResourceAppId  = "00000003-0000-0000-c000-000000000000";
    ResourceAccess =
    [Microsoft.Open.AzureAD.Model.ResourceAccess]@{
        Id   = "bf394140-e372-4bf9-a898-299cfc7564e5";
        Type = "Role"
    },
    [Microsoft.Open.AzureAD.Model.ResourceAccess]@{
        Id   = "7ab1d382-f21e-4acd-a863-ba3e13f7da61";
        Type = "Role"
    }
}

$partnerCenterAppAccess = [Microsoft.Open.AzureAD.Model.RequiredResourceAccess]@{
    ResourceAppId  = "fa3d9a0c-3fb0-42cc-9193-47c7ecd2edbd";
    ResourceAccess =
    [Microsoft.Open.AzureAD.Model.ResourceAccess]@{
        Id   = "1cebfa2a-fb4d-419e-b5f9-839b4383e05a";
        Type = "Scope"
    }
}

$SessionInfo = Get-AzureADCurrentSessionInfo

Write-Host -ForegroundColor Green "Creating the Azure AD application and related resources..."

$app = New-AzureADApplication -AvailableToOtherTenants $true -DisplayName $DisplayName -IdentifierUris "https://$($SessionInfo.TenantDomain)/$((New-Guid).ToString())" -RequiredResourceAccess $adAppAccess, $graphAppAccess, $partnerCenterAppAccess -ReplyUrls @("urn:ietf:wg:oauth:2.0:oob")
$password = New-AzureADApplicationPasswordCredential -ObjectId $app.ObjectId
$spn = New-AzureADServicePrincipal -AppId $app.AppId -DisplayName $DisplayName

if ($ConfigurePreconsent) {
    $adminAgentsGroup = Get-AzureADGroup -Filter "DisplayName eq 'AdminAgents'"
    Add-AzureADGroupMember -ObjectId $adminAgentsGroup.ObjectId -RefObjectId $spn.ObjectId
}

Write-Host "ApplicationId       = $($app.AppId)"
Write-Host "ApplicationSecret   = $($password.Value)"

[pscustomobject][ordered]@{
    ApplicationName = $DisplayName
    TenantName      = $tenantname
    TenantId        = $tenantid
    clientId        = $app.AppId
    clientSecret    = $password.value
} | Export-Csv C:\temp\azureadapps.csv -NoTypeInformation -Append

Step 2: Retrieve the initial refresh token using the Partner Center PowerShell Module

Now that we have our application set up, we can use it to retrieve our refresh token.

How to retrieve a secure app model refresh token using your new Azure AD application

  1. Ensure that you have installed the Partner Center PowerShell module by opening PowerShell as an administrator and running:
    Install-Module PartnerCenter
  2. Double click the below script to select it
  3. Copy and paste it into a new file in Visual Studio code and save it with a .ps1 extension.
  4. Update the $client_id, $client_secret and $tenant_id values with those exported from the previous script.
  5. Press F5 to run the script
  6. Sign in to Azure AD with the credentials of an account with permission to access customer tenants. Be sure to remember which account this token belongs to. If you’re using a global admin account, you may be prompted to grant consent to the application.Consent to delegated admin partner application for the secure app model
  7. Wait for the script to complete. A copy of the refresh token will be exported to C:\temp\refreshToken.txt. Be sure to delete this exported token once you have secured it within your scripts

PowerShell Script to retrieve your refresh token for the Secure App Model

$client_id = "EnterClientIDHere"
$client_secret = "EnterClientSecretHere"
$tenant_id = "EnterYourTenantIDHere"
$secpasswd = ConvertTo-SecureString $client_secret -AsPlainText -Force
$mycreds = New-Object System.Management.Automation.PSCredential ($client_id, $secpasswd)

$token = New-PartnerAccessToken -Consent -Credential $mycreds -Resource https://api.partnercenter.microsoft.com -TenantId $tenant_id
$refreshToken = $token.RefreshToken
$refreshToken | out-file C:\temp\refreshToken.txt

Step 3: Modify scripts to use the refresh token to retrieve access tokens to connect to tenants via PowerShell

Using the refresh token with the MSOnline PowerShell module

The refresh token, client ID and secret essentially replace your current credential object. You can replace your credential objects and Connect-MSOnline cmdlets with the following. Note, that the following example does not secure the refresh token at all, so just use it as a test, then make sure you secure the refresh token.

Replace the code that builds your credential objects and connects to MSOnline with this

$client_id = "EnterClientIDHere"
$client_secret = "EnterClientSecretHere"
$refreshToken = "EnterRefreshTokenHere"
$tenant_id = "EnterTenantIDHere"
function Get-GCITSAccessTokenByResource($AppCredential, $tenantid, $Resource) {
    $authority = "https://login.microsoftonline.com/$tenantid"
    $tokenEndpointUri = "$authority/oauth2/token"
    $content = @{
        grant_type = "refresh_token"
        client_id = $appCredential.appID
        client_secret = $appCredential.secret
        resource = $resource
        refresh_token = $appCredential.refreshToken
    }
    $tokenEndpointUri = "$authority/oauth2/token"

    $response = Invoke-RestMethod -Uri $tokenEndpointUri -Body $content -Method Post -UseBasicParsing
    $access_token = $response.access_token
    return $access_token
}

$AppCredential = @{
    appId        = $client_id
    secret       = $client_secret
    refreshToken = $refreshToken
}

$MSGraphToken = Get-GCITSAccessTokenByResource -Resource "https://graph.microsoft.com" -tenantid $tenant_id -AppCredential $AppCredential
$AadGraphToken = Get-GCITSAccessTokenByResource -Resource "https://graph.windows.net" -tenantid $tenant_id -AppCredential $AppCredential
Connect-MsolService -MsGraphAccessToken $MSGraphToken -AdGraphAccessToken $AadGraphToken

Using the refresh token with the AzureAD PowerShell module

Connect to own tenant via Azure AD Secure App Model

In my testing, I can connect to our own tenant’s Azure AD via the following script:

$client_id = "EnterClientIDHere"
$client_secret = "EnterClientSecretHere"
$refreshToken = "EnterRefreshTokenHere"
$tenant_id = "EnterTenantIDHere"
$delegatedAdmin = "[email protected]"

function Get-GCITSAccessTokenByResource($AppCredential, $tenantid, $Resource) {
    $authority = "https://login.microsoftonline.com/$tenantid"
    $tokenEndpointUri = "$authority/oauth2/token"
    $content = @{
        grant_type = "refresh_token"
        client_id = $appCredential.appID
        client_secret = $appCredential.secret
        resource = $resource
        refresh_token = $appCredential.refreshToken
    }
    $tokenEndpointUri = "$authority/oauth2/token"

    $response = Invoke-RestMethod -Uri $tokenEndpointUri -Body $content -Method Post -UseBasicParsing
    $access_token = $response.access_token
    return $access_token
}

$AppCredential = @{
    appId        = $client_id
    secret       = $client_secret
    refreshToken = $refreshToken
}

$MSGraphToken = Get-GCITSAccessTokenByResource -Resource "https://graph.microsoft.com" -tenantid $tenant_id -AppCredential $AppCredential
$AadGraphToken = Get-GCITSAccessTokenByResource -Resource "https://graph.windows.net" -tenantid $tenant_id -AppCredential $AppCredential
Connect-AzureAD -AadAccessToken $AadGraphToken -MsAccessToken $MSGraphToken -AccountId $delegatedAdmin

Can we use this method to run commands in customer tenants with Azure AD?

I have been able to complete the authentication to connect to a customers’ Azure AD, but have not been able to run commands. I receive an error related to an invalid domain that appears to be related to the TenantDomain property being populated with the TenantId:

Connect-AzureAD -AadAccessToken $AadGraphToken -MsAccessToken $MSGraphToken -AccountId $delegatedAdmin -TenantId $customer.tenantid

Connect Delegated Azure AD Customer Tenants

Can we use the refresh token with Exchange Online?

At this stage,  we can’t use this method to connect to customers Exchange Online environments. I’m also not sure how the new requirements will affect delegated connections to Exchange Online.

Our current method is to whitelist our own IP for MFA and authenticate to customers’ Exchange environments using basic auth. It’s very possible that this method will stop working once the new requirements are enforced. Unfortunately, the Exchange Online Remote PowerShell Module that supports MFA does not support delegated admin access at this time.

Remember to secure the refresh token

The refresh token, client Id and secret replaces the credentials you would usually use to run your delegated admin scripts. Microsoft recommends you use Azure Key Vault to secure this token.

Thoughts on the secure app model for delegated administration via PowerShell

I think increasing security of delegated admin accounts is a good move, however the model can be difficult to implement when following the documentation. The new method also doesn’t replace the connection methods that partners have relied on for some time – especially for delegated admin Exchange connections.

Although I’ve had issues getting the implementation right for the AzureAD module, as well as the newer Azure ‘Az’ module, I think it’s worth posting this for the MSOnline model with a plan to update the article once I’ve worked out the issues with the Azure AD/Az module connections.

Was this article helpful?

Related Articles