functions/Get-AadToken.ps1

<#
.SYNOPSIS
Get Access Token from Azure AD token endpoint.
 
.DESCRIPTION
Get Access Token from Azure AD token endpoint.
 
.PARAMETER ClientId
Provide Client ID or App ID required to get a access token
 
.PARAMETER ResourceId
Provide App URI ID or Service Principal Name you want to get an access token for.
 
.PARAMETER Scopes
Provide Scopes for the request.
 
.PARAMETER ClientSecret
Provide Client Secret if this is a Web App client.
 
.PARAMETER Redirect
Provide Redirect URI or Redirect URL.
 
.PARAMETER Username
Provide username if using Resource Owner Password grant flow.
 
.PARAMETER Password
Provide Password of resource owner if using Resource Owner Password Credential grant flow.
 
.PARAMETER Tenant
Provide Tenant ID you are authenticating to.
 
.PARAMETER Instance
Provide Azure AD Instance you are connecting to.
 
.PARAMETER UseV2
By default, Azure AD V1 authentication endpoints will be used. Use this if you want to use the V2 Authentication endpoints.
 
.PARAMETER UseResourceOwner
Enable this switch if you want to use the Resource Owner Password Credential grant flow.
 
.PARAMETER UseClientCredential
Enable this switch if you want to use the Client Credential grant flow.
 
.PARAMETER UseRefreshToken
Enable this switch if you want to use the Refresh Token grant flow.
 
.PARAMETER GrantType
Specify the 'grant_type' you want to use.
 - password
 - client_credentials
 - refresh_token
 - authorization_code
 - urn:ietf:params:oauth:grant-type:jwt-bearer
 - urn:ietf:params:oauth:grant-type:saml1_1-bearer
 - urn:ietf:params:oauth:grant-type:saml2-bearer
 
.PARAMETER Code
Specify the 'code' for authorization code flow
 
.PARAMETER Assertion
Specify the 'assertion' you want to use for user assertion
 
.PARAMETER ClientAssertionType
Specify the 'client_assertion_type' you want to use.
 
Available options...
 - urn:ietf:params:oauth:client-assertion-type:jwt-bearer
 
.PARAMETER ClientAssertion
Specify the 'client_assertion' you want to use. This is used for Certificate authentication where the client assertion is signed by a private key.
 
.PARAMETER RequestedTokenUse
Specify the 'requested_token_use' you want to use. For Azure AD, the only acceptable value is 'on_behalf_of'. This is used to identify the on-behalf-of flow is to be used.
 
.PARAMETER RequestedTokenType
Specify the 'requested_token_type'. This is either...
 - urn:ietf:params:oauth:token-type:saml2
 - urn:ietf:params:oauth:token-type:saml1
 
.EXAMPLE
Use Resource Owner Password Credential grant flow
Get-AadToken -UseResourceOwner -ResourceId "https://graph.microsoft.com" -ClientId 5567ba8a-e608-4219-97d8-3d3ea63718e7 -Redirect "https://login.microsoftonline.com/common/oauth2/nativeclient" -Username john@contoso.com -Password P@$$w0rd!
 
To get client only access token
Get-AadToken -UseClientCredential -ResourceId "https://graph.microsoft.com" -ClientId 5567ba8a-e608-4219-97d8-3d3ea63718e7 -ClientSecret "98iwjdc-098343js="
 
.NOTES
General notes
#>


function Get-AadToken
{
    [CmdletBinding(DefaultParameterSetName="All")] 
    param(
        [Parameter(Mandatory=$true, Position=0)]
        [string]$ClientId,

        [Parameter(ParameterSetName="UseResourceOwner", Mandatory=$false)]
        [switch]$UseResourceOwner,

        [Parameter(ParameterSetName="UseClientCredential", Mandatory=$false)]
        [switch]$UseClientCredential,

        [Parameter(ParameterSetName="UseRefreshToken", Mandatory=$false)]
        [switch]$UseRefreshToken,   

        [Parameter(ParameterSetName="UseResourceOwner", Mandatory=$true)]
        [string]$Username = $null,

        [Parameter(ParameterSetName="UseResourceOwner", Mandatory=$true)]
        [string]$Password = $null,


        [string]$ClientSecret = $null,

        [Parameter(ParameterSetName="UseRefreshToken", Mandatory=$true)]
        [string]$RefreshToken,

        [string]$ResourceId = $($Global:AadSupport.Resources.MsGraph),
        
        [Parameter(ParameterSetName="UseClientCredential", Mandatory=$true)]
        [Parameter(ParameterSetName="UseRefreshToken", Mandatory=$false)]
        [Parameter(ParameterSetName="UseResourceOwner", Mandatory=$false)]
        [string]$Tenant,

        [string]$Scopes = "openid email profile offline_access $($Global:AadSupport.Resources.MsGraph)/.default",
        [string]$Redirect = $null,
        [string]$Instance = "https://login.microsoftonline.com",
        
        [switch]$UseV2 = $false,

        [string]$GrantType,
        [string]$Code,
        [string]$Assertion,
        [string]$ClientAssertionType,
        [string]$ClientAssertion,
        [string]$RequestedTokenUse,

        [ValidateSet("urn:ietf:params:oauth:token-type:saml2", "urn:ietf:params:oauth:token-type:saml1")]
        [string]$RequestedTokenType
    )

    # REQUIRE AadSupport Session
    RequireConnectAadSupport
    # END REGION

    $error.Clear()
    $scriptError = $null

    # Get Service Principal
    try {
        $sp = Get-AadServicePrincipal -Id $ClientId

        # Get real Client ID
        $ClientId = $sp.AppId
    }
    catch {
        Write-Host "App '$ClientId' not found" -ForegroundColor Yellow
    }


    if($sp.count -gt 1)
    {
        throw "Found too many results for '$ClientId'. Please specify a unique ClientId."
    }


    # Get Redirect Uri if not one specified
    if(-not $Redirect -and $sp) 
    {
        $Redirect = $sp.ReplyUrls[0]
    }
    

    # Set Grant_Type
    if (-not $UseResourceOwner -and -not $UseClientCredential -and -not $UseRefreshToken -and -not $GrantType)
    {
        do {
            $isValidChoice = $true
            Write-Host ""
            Write-Host "Do you want to..." -ForegroundColor Yellow
            Write-Host "1: Resource Owner (Provice user name and password)"
            Write-Host "2: Client Credential (Provice Client ID and Client Secret)"
            Write-Host "3: Refresh (Provide Refresh Token)"
            Write-Host "Type 'exit' to quit."
            Write-Host ""
            $choice = Read-Host -Prompt "Enter your choice (#)"
            Write-Host ""
            switch ($choice) 
            {
                "1" 
                {
                    $UseResourceOwner = $true 
                    break
                }

                "2" 
                {
                    $UseClientCredential = $true 
                    break
                }

                "3" 
                {
                    $UseRefreshToken = $true
                    break
                }

                "exit" {return}

                "default" 
                {
                    $isValidChoice = $false; 
                    break
                }
            }
        } while (-not $isValidChoice)
    }

    # Requirements for Resource Owner
    if ($UseResourceOwner)
    {
        $GrantType = "password"
        
        if (-not $Username)
        {
            $Username = Read-Host -Prompt "Username"
        }

        if (-not $Password)
        {
            $Password = Read-Host -Prompt "Password"
        }
    }

    # Requirements for ClientCredentials
    if($UseClientCredential)
    {
        $GrantType = "client_credentials"

        if (-not $Tenant -or $Tenant -eq "common")
        {
            $Tenant = Read-Host -Prompt "Tenant (Do not use 'common')"
        }

        if (-not $ClientSecret)
        {
            $ClientSecret = Read-Host -Prompt "ClientSecret"
        }
    }

    # Requirements for Refresh Token
    if($UseRefreshToken)
    {
        $GrantType = "refresh_token"
        if (-not $RefreshToken)
        {
            $RefreshToken = Read-Host -Prompt "Refresh Token"
        }
    }



    # Set default Tenant
    if (-not $Tenant)
    {
        $Tenant = "common"
    }


    # Start initializing the Token result object
    $token = @{}

    # Start initializing the token endpoint POST content
    $body            = @{}


    # Lookup Resource
    try {
        $resource = Get-AadServicePrincipal -Id $ResourceId
        $ResourceId - $resource.AppId
    }
    catch {
        Write-Host "Resource '$ResourceId' not found" -ForegroundColor Yellow
    }


    # Set up if AAD V1 or V2 authentication is used...
    if ($UseV2) {
        # Use V2 endpoint
        $authUrl = "$Instance/$Tenant/oauth2/v2.0/token"
    }

    else {
        # Use V1 endpoint
        $authUrl = "$Instance/$Tenant/oauth2/token"
        $body.resource = "$ResourceId"
    }

    $token.authUrl = $authUrl

    $body.client_id  = $ClientID
    $body.grant_type = $GrantType
    $body.scope      = $scopes
    $body.nonce      = 1234
    $body.state      = 5678

    if ($Redirect)
    {
        $body.redirect_uri = $Redirect
    }

    if ($RefreshToken)
    {
        $body.refresh_token = $RefreshToken
    }

    if($Code)
    {
        $body.code = $Code
    }

    if ($ClientSecret) {
        $body.client_secret = $ClientSecret
    }

    if ($Username -and $Password) {
        
        $body.username   = $Username
        $body.password   = $Password
        $body.grant_type = "password"
    }

    if($ClientAssertionType)
    {
        $body.client_assertion_type = $ClientAssertionType
    }

    if($ClientAssertion)
    {
        $body.client_assertion = $ClientAssertion
    }

    if($Assertion)
    {
        $body.assertion = $Assertion
    }

    if($RequestedTokenUse)
    {
        $body.requested_token_use = $RequestedTokenUse
    }

    if($RequestedTokenType)
    {
        $body.requested_token_type = $RequestedTokenType
    }

    $token.PostContent = $body

    # Sign-in to Azure AD & Get Access Token
    try {
        Write-Verbose "Authenticating to '$authUrl'"
        
        $response = $null
        $response      = Invoke-WebRequest -Method Post -Uri $authUrl -Body $body -verbose

        $content = $null

        if($response.content) {
            $content = $response.content | ConvertFrom-Json
        }
        elseif($response.access_token) {
            $content = $response
        }

        if ($content.access_token)   { $token.AccessToken = $content.access_token }
        if ($content.id_token)       { $token.IdToken = $content.id_token }
        if ($content.refresh_token)  { $token.RefreshToken = $content.refresh_token }
        if ($content.Type)           { $token.Type = $content.Type }
        if ($content.scope)          { $token.Scopes = $content.scope }
        if ($content.expires_in)     { $token.expires_in = $content.expires_in }
        if ($content.ext_expires_in) { $token.ext_expires_in = $content.ext_expires_in }
        if ($content.expires_on)     { $token.expires_on = $content.expires_on }
        if ($content.not_before)     { $token.not_before = $content.not_before }
        if ($content.resource)       { $token.resource = $content.resource }

        if($token.AccessToken)
        {
            if($token.AccessToken.StartsWith("eyJ"))
            {
                $token.AccessTokenClaims = $token.AccessToken | ConvertFrom-AadJwtToken
            }

            else 
            {
                $token.SamlDecoded = ConvertFrom-Base64String -base64String ( Base64UrlDecode($token.AccessToken) )
            }
            
        }

        if($token.IdToken)
        {
            $token.IdTokenClaims = $token.IdToken | ConvertFrom-AadJwtToken
        }
    }

    # Acquire token failed
    Catch {
        
        $scriptError = $_
        $scriptError = $scriptError | ConvertFrom-Json

        $token.Error = $scriptError
        $scriptError | Out-Host

        # AADSTS65001
        if ($scriptError -match "AADSTS65001") {
            $consentUrl = "$Instance/$Tenant/oauth2/authorize?client_id=$ClientId&response_type=code&redirect=$RedirectUri&prompt=admin_consent"
            Write-Host ""
            Write-Host "Consent Required" -ForegroundColor Yellow
            Write-Host ""
            Write-Host "Go to the following url '$consentUrl'" -ForegroundColor Yellow
        }

        # AADSTS50079
        if ($scriptError -match "AADSTS50079") {

            $claims = $scriptError.claims | ConvertFrom-Json
            $capolids = $claims.access_token.capolids.values

            Write-Host ""
            Write-Host "User Interaction Required due to Conditional Access Policy Id '$capolids'" -ForegroundColor Yellow

        }

        return $token
    }

    

    $Object = New-Object PSObject -Property $token

    if($Object.AccessTokenClaims -and -not $HideOutput)
    {
        
        Write-Host ""
        Write-Host "Access Token" -ForegroundColor Yellow
        Write-ObjectToHost $($Object.AccessTokenClaims)
    }
    
    
    return $Object
}






# ##############################################################################################
# CLIENT ASSERTION
# ##############################################################################################
function ClientAssertion 
{
    param($ClientId, $ClientSecret, $ClientAssertion)

    $body            = @{}
    $body.client_id  = $ClientId
    $body.client_secret  = $ClientSecret
    $body.refresh_token  = [System.Web.HttpUtility]::UrlEncode($RefreshToken)
    $body.grant_type = "refresh_token"

    try {
        Write-Host "Authenticating to '$authUrl'" -ForegroundColor Yellow
        $body | ft
        
        $response = $null
        $response      = Invoke-WebRequest -Method Post -Uri $authUrl -Body $body -verbose
    
        $content = $null
    
        if($response.content) {
            $content = $response.content | ConvertFrom-Json
        }
        elseif($response.access_token) {
            $content = $response
        }
        
        $AccessToken = $null
        $AccessToken = $content.access_token
    
        if($AccessToken) {
            Write-Host "Acquired Access Token successfull!"
        }
    }

    Catch {
        $scriptError = $_
    
        if ($response) {
            $scriptError = $scriptError | ConvertFrom-Json
            $scriptError
        } 
        else {
                throw $scriptError
        }
    }
}