Search code examples
oauth-2.0oauthazure-web-app-servicemicrosoft-entra-id

User impersonation in Microsoft Entra ID App


I have 3 different App Registration Apps. All are backend APIs and using azure-identity module for Authentication. For better security, we're using Managed Identity to authenticate with KeyVaults and other Azure Services to avoid Client Secrets. I'm trying to figure out how to authenticate on behalf of the user to my other Apps in Azure + I need to access Azure DevOps APIs on behalf of the user.

What I've already tried and hoped would work, but it didn't: in my App1->API Permissions-> added Azure DevOps user_impersonation permission.

Using az cli I get an access token to my App1: az account get-access-token --resource "api://<app1-client-id>"

I can use the token to access my App1 API no problem, but I can't pass the same token to Azure DevOps API and just get "Azure DevOps Sing in" page back from the API.

Am I missing some configuration or conceptually doing something wrong? Do I need to exchange existing token for another token that can be used with Azure DevOps?

Similar question: How to impersonate user of other Azure AD App?


Solution

  • Adding Azure DevOps user_impersonation via App1->API Permissions to your app is just the first step. This will allow you to exchange your App1 user token for an Azure DevOps user token.

    The token exchange needs to happen with the on-behalf-of flow. I think the scope of this token exchange request needs to be 499b84ac-1321-427f-aa17-267ca6975798/.default. See this section in the Azure DevOps documentation.

    Using curl the on-behalf-of request should look like this:

    curl https://login.microsoftonline.com/${AZURE_TENANT_ID}/oauth2/v2.0/token \
      -d 'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer' \
      -d 'requested_token_use=on_behalf_of' \
      -d 'scope=499b84ac-1321-427f-aa17-267ca6975798/.default' \
      -d 'client_id=${APP_1_CLIENT_ID}' \
      -d 'client_secret=${APP_1_CLIENT_SECRET}' \
      -d "assertion=${APP_1_USER_TOKEN}"
    

    Note: This token exchange must happen with a shared secret. There is also a way documented to exchange a token with a jwt-bearer client assertion, but attempting to exchange a user token fails with error code AADSTS700229. See this GitHub issue.