Search code examples
azurejwtazure-climicrosoft-entra-idazure-app-registration

Use delegated permissions with Client App Registration


I want to use an App Registration, ClientApp, to use a REST API endpoint, for which I've created a second App Registration, APIApp.

I want to acquire a token with ClientApp using delegated permissions instead of app roles, but the following code isn't working.

I'm using the instructions in Microsoft Learn to target /oauth2/devicecode then /oauth2/v2.0/token:

# https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-device-code
[guid]$TenantId = "..."
[guid]$ClientAppId = "..."
[guid]$APIAppId = "..."

$deviceCodeBody = @{
    client_id = $ClientAppId
    scope     = "api://$APIAppId/MyData.Read.All offline_access"
}
$deviceCodeRequest = Invoke-RestMethod `
    -Method Post `
    -Uri "https://login.microsoftonline.com/$tenantId/oauth2/devicecode" `
    -ContentType 'application/x-www-form-urlencoded' `
    -Body $deviceCodeBody 
Write-Host "`n$($deviceCodeRequest.message)"


$tokenBody = @{
    grant_type  = "urn:ietf:params:oauth:grant-type:device_code"
    device_code = $deviceCodeRequest.device_code
    client_id   = $ClientAppId
}

$tokenRequest = $null
while ([string]::IsNullOrEmpty($tokenRequest.access_token)) {
    try {
        $tokenRequest = Invoke-RestMethod `
            -Method Post `
            -Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" `
            -ContentType 'application/x-www-form-urlencoded' `
            -Body $tokenBody
    }
    catch {
        $errorMessage = $_.ErrorDetails.Message | ConvertFrom-Json
        # If not waiting for auth, throw error
        if ($errorMessage.error -ne "authorization_pending") {
            Write-Host "Error: $($errorMessage.error_description)" -ForegroundColor Red
            exit 1 # Exit script
        }
        Start-Sleep -Seconds 1
    } 
}

I could get the necessary token easier with azure-cli but then I'd require app roles (app permissions) for ClientAppId. I prefer using delegated user permissions like in this example:

[guid]$TenantId = "..."
[guid]$ClientAppId = "..."
[string]$ClientSecret = "***"
[guid]$APIAppId = "..."
az login `
    --allow-no-subscriptions --tenant $TenantId `
    --service-principal -u $ClientAppId -p $ClientSecret `
    --query accessToken --output tsv
$accessToken = az account get-access-token `
    --scope 'api://$APIAppId/.default' `
    --query accessToken --output tsv

Can you please explain what I'm doing wrong? When I use the devicelogin method I get the following error

AADSTS7000218: The request body must contain the following parameter: 'client_assertion' or 'client_secret'.

Should I get the token for ClientAppId in any other way?


Solution

  • I got same error message, When I ran the script which you provided:

    enter image description here

    To resolve this error, Configure Authentication Tab of Client-App like below:

    enter image description here enter image description here

    Now, Generated Client Secret for Client-App:

    enter image description here

    Use below modified script:

    [guid]$TenantId = "<tenant-id>"
    [guid]$ClientAppId = "<client-app-id>
    [guid]$APIAppId = "<API-app-ID>"
    
    # Step 1: Request device code
    $deviceCodeBody = @{
        client_id = $ClientAppId
        scope     = "api://$APIAppId/MyData.Read.All offline_access"
    }
    $deviceCodeRequest = Invoke-RestMethod `
        -Method Post `
        -Uri "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/devicecode" `
        -ContentType 'application/x-www-form-urlencoded' `
        -Body $deviceCodeBody
    Write-Host "`n$($deviceCodeRequest.message)" # Prompt the user
    
    # Step 2: Poll for token
    $tokenBody = @{
        grant_type    = "urn:ietf:params:oauth:grant-type:device_code"
        device_code   = $deviceCodeRequest.device_code
        client_id     = $ClientAppId
        client_secret = "<client-secret>"  # Replace with your actual client secret after configuring authentication tab
    }
    
    
    $accessToken = $null
    while ($accessToken -eq $null) {
        try {
            $tokenResponse = Invoke-RestMethod `
                -Method Post `
                -Uri "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token" `
                -ContentType 'application/x-www-form-urlencoded' `
                -Body $tokenBody
            $accessToken = $tokenResponse.access_token
        } catch {
            $errorMessage = $_.ErrorDetails.Message | ConvertFrom-Json
            if ($errorMessage.error -ne "authorization_pending") {
                Write-Host "Error: $($errorMessage.error_description)" -ForegroundColor Red
                exit 1
            }
            Start-Sleep -Seconds 2
        }
    }
    
    # Step 3: Use the access token
    Write-Host "`nAccess Token:`n$accessToken"
    
    

    Response:

    enter image description here

    When I decoded this generated access token at https://jwt.ms :

    enter image description here