question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Update PowerPlatformCommand base class with authentication options

See original GitHub issue

Since we are working on #2917 and #2919 (and more to come as we move along) we should introduce a new type of command class. The PPCommand. That base class should have the following:

  • logic to handle different environments
  • handle all Power Platform services based on this environment

The PowerShell samples use a -Endpoint parameter within the Add-PowerAppsAccount cmdlet (and picks the default production endpoint when none is specified). Given the nature of the CLI I feel that we should use a slightly different approach. For me it makes the most sense to use the m365 cli config set and add an additional parameter there called PowerPlatformEndpoint. Should be a string value with a few options (Valid options are “dod”, “prod”,“preview”,“tip1”, “tip2”, “usgov”, or “usgovhigh”.)

@pnp/cli-for-microsoft-365-maintainers how do you feel about that?

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:2
  • Comments:20 (20 by maintainers)

github_iconTop GitHub Comments

2reactions
appieschotcommented, Oct 10, 2022

@pnp/cli-for-microsoft-365-maintainers I have the feeling we can close this one; feels like there is not too much interest on this and we should focus on implementing the other pp commands first. What do you think?

2reactions
appieschotcommented, Feb 4, 2022

Okey there is a little bit of magic in place for for the audience so I am not 100% sure if we can replicate that (all code below); but it also doesn’t feel like something should be possible. Will try to find some time to do a PoC for CLI.

The current implementation in PowerShell is as follows: Step 1: Pass the EndPoint Parameter

        [ValidateSet("prod","preview","tip1", "tip2", "usgov", "usgovhigh", "dod", "china")]
        [string]$Endpoint = "prod",

Step 3: Determine the Audience (remark that the PowerShell commandlets allow you to set a different audience URL

    if ($Audience -eq "https://service.powerapps.com/")
    {
        # It's the default audience - we should remap based on endpoint as needed
        $Audience = Get-DefaultAudienceForEndPoint($Endpoint)
    }
 ...
    $audienceMapping = @{
        "prod" = "https://service.powerapps.com/";
        "preview" = "https://service.powerapps.com/";
        "tip1"= "https://service.powerapps.com/";
        "tip2"= "https://service.powerapps.com/";
        "usgov"= "https://gov.service.powerapps.us/";
        "usgovhigh"= "https://high.service.powerapps.us/";
        "dod" = "https://service.apps.appsplatform.us/";
        "china" = "https://service.powerapps.cn/";
    }

    if ($null -ne $audienceMapping[$Endpoint])
    {
        return $audienceMapping[$Endpoint];
    }

Then there is this large internal method to set the PowerAppsAcc that holds all that logic and does an extra Switch to determine the correct URL.

function Add-PowerAppsAccountInternal
{
    param
    (
        [string] $Audience = "https://service.powerapps.com/",

        [Parameter(Mandatory = $false)]
        [ValidateSet("prod","preview","tip1", "tip2", "usgov", "usgovhigh", "dod", "china")]
        [string]$Endpoint = "prod",

        [string]$Username = $null,

        [SecureString]$Password = $null,

        [string]$TenantID = $null,

        [string]$CertificateThumbprint = $null,

        [string]$ClientSecret = $null,

        [string]$ApplicationId = "1950a258-227b-4e31-a9cf-717495945fc2"
    )

    [string[]]$scopes = "$Audience/.default"
    if ([string]::IsNullOrWhiteSpace($ApplicationId))
    {
        $ApplicationId = "1950a258-227b-4e31-a9cf-717495945fc2"
    }

    Write-Debug "Using appId, $ApplicationId"

    [Microsoft.Identity.Client.IClientApplicationBase]$clientBase = $null
    [Microsoft.Identity.Client.AuthenticationResult]$authResult = $null

    if ($global:currentSession.loggedIn -eq $true -and $global:currentSession.recursed -ne $true)
    {
        Write-Debug "Already logged in, checking for token for resource $Audience"
        $authResult = $null
        if ($global:currentSession.resourceTokens[$Audience] -ne $null)
        {
            if ($global:currentSession.resourceTokens[$Audience].accessToken -ne $null -and `
                $global:currentSession.resourceTokens[$Audience].expiresOn -ne $null -and `
                $global:currentSession.resourceTokens[$Audience].expiresOn -gt (Get-Date))
            {
                Write-Debug "Token found and value, returning"
                return
            }
            else
            {
                 # Already logged in with an account, silently asking for a token from MSAL which should refresh
                try
                {
                    Write-Debug "Already logged in, silently requesting token from MSAL"
                    $authResult = $global:currentSession.msalClientApp.AcquireTokenSilent($scopes, $global:currentSession.msalAccount).ExecuteAsync() | Await-Task
                }
                catch [Microsoft.Identity.Client.MsalUiRequiredException] 
                {
                    Write-Debug ('{0}: {1}' -f $_.Exception.GetType().Name, $_.Exception.Message)
                }
            }
        }

        if ($authResult -eq $null)
        {
            Write-Debug "No token found, reseting audience and recursing: $Audience"
            # Reset the current audience values and call Add-PowerAppsAccount again
            $global:currentSession.resourceTokens[$Audience] = $null
            $global:currentSession.recursed = $true

            Add-PowerAppsAccountInternal -Audience $Audience -Endpoint $Endpoint -Username $global:currentSession.username -Password $global:currentSession.password -TenantID $global:currentSession.InitialTenantId -CertificateThumbprint $global:currentSession.certificateThumbprint -ClientSecret $global:currentSession.clientSecret -ApplicationId $global:currentSession.applicationId
            $global:currentSession.recursed = $false

            # Afer recursing we can early return
            return
        }
    }
    else
    {
        [string] $jwtTokenForClaims = $null
        [Microsoft.Identity.Client.AzureCloudInstance] $authBaseUri =
            switch ($Endpoint)
                {
                    "usgov" { [Microsoft.Identity.Client.AzureCloudInstance]::AzurePublic }
                    "usgovhigh" { [Microsoft.Identity.Client.AzureCloudInstance]::AzureUsGovernment }
                    "dod"       { [Microsoft.Identity.Client.AzureCloudInstance]::AzureUsGovernment }
                    "china"       { [Microsoft.Identity.Client.AzureCloudInstance]::AzureChina }
                    default     { [Microsoft.Identity.Client.AzureCloudInstance]::AzurePublic }
                };

        [Microsoft.Identity.Client.AadAuthorityAudience] $aadAuthAudience = [Microsoft.Identity.Client.AadAuthorityAudience]::AzureAdAndPersonalMicrosoftAccount
        if ($Username -ne $null -and $Password -ne $null)
        {
            $aadAuthAudience = [Microsoft.Identity.Client.AadAuthorityAudience]::AzureAdMultipleOrgs
        }

        Write-Debug "Using $aadAuthAudience : $Audience : $ApplicationId"

        if (![string]::IsNullOrWhiteSpace($TenantID) -and `
            (![string]::IsNullOrWhiteSpace($ClientSecret) -or ![string]::IsNullOrWhiteSpace($CertificateThumbprint)))
        {
            $options = New-Object -TypeName Microsoft.Identity.Client.ConfidentialClientApplicationOptions
            $options.ClientId = $ApplicationId
            $options.TenantId = $TenantID

            [Microsoft.Identity.Client.IConfidentialClientApplication ]$ConfidentialClientApplication = $null

             if (![string]::IsNullOrWhiteSpace($CertificateThumbprint))
            {
                Write-Debug "Using certificate for token acquisition"
                $clientCertificate = Get-Item -Path Cert:\CurrentUser\My\$CertificateThumbprint
                $ConfidentialClientApplication = [Microsoft.Identity.Client.ConfidentialClientApplicationBuilder ]::Create($ApplicationId).WithCertificate($clientCertificate).Build()
            }
            else
            {
                Write-Debug "Using clientSecret for token acquisition"
                $ConfidentialClientApplication = [Microsoft.Identity.Client.ConfidentialClientApplicationBuilder ]::Create($ApplicationId).WithClientSecret($ClientSecret).Build()
            }

            $authResult = $ConfidentialClientApplication.AcquireTokenForClient($scopes).WithAuthority($authBaseuri, $TenantID, $true).ExecuteAsync() | Await-Task
            $clientBase = $ConfidentialClientApplication
        }
        else 
        {
            [Microsoft.Identity.Client.IPublicClientApplication]$PublicClientApplication = $null
            $PublicClientApplication = [Microsoft.Identity.Client.PublicClientApplicationBuilder]::Create($ApplicationId).WithAuthority($authBaseuri, $aadAuthAudience, $true).WithDefaultRedirectUri().Build()

            if ($Username -ne $null -and $Password -ne $null)
            {
                Write-Debug "Using username, password"
                $authResult = $PublicClientApplication.AcquireTokenByUsernamePassword($scopes, $UserName, $Password).ExecuteAsync() | Await-Task
            }
            else
            {
                Write-Debug "Using interactive login"
                $authResult = $PublicClientApplication.AcquireTokenInteractive($scopes).ExecuteAsync() | Await-Task
            }
            $clientBase = $PublicClientApplication
        }
    }

    if ($authResult -ne $null)
    {
        if (![string]::IsNullOrWhiteSpace($authResult.IdToken))
        {
            $jwtTokenForClaims = $authResult.IdToken
        }
        else
        {
            $jwtTokenForClaims = $authResult.AccessToken
        }

        $claims = Get-JwtTokenClaims -JwtToken $jwtTokenForClaims

        if ($global:currentSession.loggedIn -eq $true)
        {
           Write-Debug "Adding new audience to resourceToken map. Expires $authResult.ExpiresOn"
            # addition of a new token for a new audience
            $global:currentSession.resourceTokens[$Audience] = @{
                accessToken = $authResult.AccessToken;
                expiresOn = $authResult.ExpiresOn;
            };
        }
        else
        {
            Write-Debug "Adding first audience to resourceToken map. Expires $authResult.ExpiresOn"
            $global:currentSession = @{
                loggedIn = $true;
                recursed = $false;
                msalClientApp = $clientBase;
                msalAccount = $authResult.Account;
                upn = $claims.upn;
                InitialTenantId = $TenantID;
                tenantId = $claims.tid;
                userId = $claims.oid;
                applicationId = $ApplicationId;
                username = $Username;
                password = $Password;
                certificateThumbprint = $CertificateThumbprint;
                clientSecret = $ClientSecret;
                resourceTokens = @{
                    $Audience = @{
                        accessToken = $authResult.AccessToken;
                        expiresOn = $authResult.ExpiresOn;
                    }
                };
                selectedEnvironment = "~default";
                flowEndpoint = 
                    switch ($Endpoint)
                    {
                        "prod"      { "api.flow.microsoft.com" }
                        "usgov"     { "gov.api.flow.microsoft.us" }
                        "usgovhigh" { "high.api.flow.microsoft.us" }
                        "dod"       { "api.flow.appsplatform.us" }
                        "china"     { "api.powerautomate.cn" }
                        "preview"   { "preview.api.flow.microsoft.com" }
                        "tip1"      { "tip1.api.flow.microsoft.com"}
                        "tip2"      { "tip2.api.flow.microsoft.com" }
                        default     { throw "Unsupported endpoint '$Endpoint'"}
                    };
                powerAppsEndpoint = 
                    switch ($Endpoint)
                    {
                        "prod"      { "api.powerapps.com" }
                        "usgov"     { "gov.api.powerapps.us" }
                        "usgovhigh" { "high.api.powerapps.us" }
                        "dod"       { "api.apps.appsplatform.us" }
                        "china"     { "api.powerapps.cn" }
                        "preview"   { "preview.api.powerapps.com" }
                        "tip1"      { "tip1.api.powerapps.com"}
                        "tip2"      { "tip2.api.powerapps.com" }
                        default     { throw "Unsupported endpoint '$Endpoint'"}
                    };            
                bapEndpoint = 
                    switch ($Endpoint)
                    {
                        "prod"      { "api.bap.microsoft.com" }
                        "usgov"     { "gov.api.bap.microsoft.us" }
                        "usgovhigh" { "high.api.bap.microsoft.us" }
                        "dod"       { "api.bap.appsplatform.us" }
                        "china"     { "api.bap.partner.microsoftonline.cn" }
                        "preview"   { "preview.api.bap.microsoft.com" }
                        "tip1"      { "tip1.api.bap.microsoft.com"}
                        "tip2"      { "tip2.api.bap.microsoft.com" }
                        default     { throw "Unsupported endpoint '$Endpoint'"}
                    };      
                graphEndpoint = 
                    switch ($Endpoint)
                    {
                        "prod"      { "graph.windows.net" }
                        "usgov"     { "graph.windows.net" }
                        "usgovhigh" { "graph.windows.net" }
                        "dod"       { "graph.windows.net" }
                        "china"     { "graph.windows.net" }
                        "preview"   { "graph.windows.net" }
                        "tip1"      { "graph.windows.net"}
                        "tip2"      { "graph.windows.net" }
                        default     { throw "Unsupported endpoint '$Endpoint'"}
                    };
                cdsOneEndpoint = 
                    switch ($Endpoint)
                    {
                        "prod"      { "api.cds.microsoft.com" }
                        "usgov"     { "gov.api.cds.microsoft.us" }
                        "usgovhigh" { "high.api.cds.microsoft.us" }
                        "dod"       { "dod.gov.api.cds.microsoft.us" }
                        "china"     { "unsupported" }
                        "preview"   { "preview.api.cds.microsoft.com" }
                        "tip1"      { "tip1.api.cds.microsoft.com"}
                        "tip2"      { "tip2.api.cds.microsoft.com" }
                        default     { throw "Unsupported endpoint '$Endpoint'"}
                    };
            };
        }
    }
}

Read more comments on GitHub >

github_iconTop Results From Across the Web

pac auth - Power Platform - Microsoft Learn
Describes commands and parameters for the Microsoft Power Platform CLI auth command group.
Read more >
Important changes (deprecations) coming in Power Apps and ...
Important. "Deprecated" means we intend to remove the feature or capability from a future release. The feature or capability will continue ...
Read more >
Power Platform Developers tool – October update
Power platform Command line updates. Model builder. We are bringing the ability to generate C# classes for Dataverse ... auth with secrets.
Read more >
Get started with configuring your portal authentication - Power ...
Go to Power Apps. · On the left pane, select Apps. Select Apps. · Select your portal from the list of available apps....
Read more >
Microsoft Power Platform CLI
pac install latest command is not applicable for Power Platform Tools for Visual Studio Code. It will look for updates and update automatically ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found