azureazure-active-directoryimpersonationazure-application-registration

Azure User Impersonation does not work when User/Assignment enable on the Backend App Registration


I have two application registrations: one for the backend and one for the frontend.

On the backend App Reg, I have defined (exposed) a scope called User_impersonation like the following:

enter image description here

On the frontend App Reg, I have added the permissions (User_impersonation) like the following:

enter image description here

Also on the frontend side, I enabled user/group assignment and assigned a security group; so that only the members of that security group can access/query this frontend.

enter image description here

So far, when a user queries the frontend with an access token (requested from the frontend app reg); everything works fine: the frontend app reg requests an access token from the backend and forwards the request of the user.

However, when I try to enable the user/group assignment on the backend app reg (by adding the same security group); the frontend app can no longer query the backend (User_impersonation fails); like the query times out with no reply from the backend.

Can someone help me make the User_impersonation work when the group assignment is enabled on both app regs (not only the frontend). Thanks.

This is how the client/user requests access to the webapp/frontend:


from azure.identity import DefaultAzureCredential, ClientSecretCredential

authority="https://login.microsoftonline.com/{TENANT_ID}/v2.0"

credential = DefaultAzureCredential(authority=authority, exclude_shared_token_cache_credential=True)
scope = "api://{frontend_clientid}/.default"
minerva_webapp_access_token = credential.get_token(scope).token

and this is how the webapp/frontend requests access to the backend app reg:


credential = ClientSecretCredential(
    tenant_id=TENANT_ID,
    client_id=WEBAPP_CLIENT_ID,
    client_secret=webapp_client_secret)
scope = (                "api://{minerva_python39_backend_app_client_id}/.default".format(
                        minerva_python39_backend_app_client_id=MINERVA_PYTHON39_CLIENTID))

access_token = credential.get_token(scope).token


Solution

  • Note that, client credentials flow works only with permissions of Application type. This flow won't work with User_impersonation as it is Delegated permission.

    I registered 2 applications in my Azure AD tenant same as you: one for the backend and one for the frontend.

    On the Backend application, I defined (exposed) User_impersonation scope like below:

    enter image description here

    On the Frontend application, I added User_impersonation API permission:

    enter image description here

    Now, I generated access token using client credentials with same code as you like below:

    credential = ClientSecretCredential(
        tenant_id=TENANT_ID,
        client_id=WEBAPP_CLIENT_ID,
        client_secret=webapp_client_secret)
    scope = (                "api://{minerva_python39_backend_app_client_id}/.default".format(
                            minerva_python39_backend_app_client_id=MINERVA_PYTHON39_CLIENTID))
    
    access_token = credential.get_token(scope).token
    print(access_token)
    

    Response:

    enter image description here

    When I decode the token by pasting it in jwt.ms, it has right audience in aud but missing scp claim like below:

    enter image description here

    To make User_impersonation work when the group assignment is enabled on both app regs, your access token should have scp claim with that specific permission.

    For that, you need to use Delegated flows like authorization code flow, interactive flow, username password flow etc... for generating tokens.

    In my case, I used authorization code flow to generate access token for which code is needed. I ran below authorization request in browser and got code successfully in address bar:

    https://login.microsoftonline.com/<tenantId>/oauth2/v2.0/authorize? 
    client_id=<frontend_appId>
    &redirect_uri=https://jwt.ms
    &response_type=code  
    &response_mode=query  
    &scope= api://<backend_appID>/User_impersonation
    &state=12345
    

    Code:

    enter image description here

    I ran below python code and got access token using authorization code flow in response like this:

    from azure.identity import AuthorizationCodeCredential
    
       credential = AuthorizationCodeCredential(
           tenant_id=TENANT_ID,
           client_id=WEBAPP_CLIENT_ID,
           authorization_code="<auth_code_from_above>",
           redirect_uri="https://jwt.ms",
       )
    
    scope = ( "api://{minerva_python39_backend_app_client_id}/.default".format(
                            minerva_python39_backend_app_client_id=MINERVA_PYTHON39_CLIENTID))
    
    access_token = credential.get_token(scope).token
    print(access_token)
    

    Response:

    enter image description here

    When I decode the token by pasting it in jwt.ms, it has right audience in aud and User_impersonation permission in scp claim like below:

    enter image description here

    In your case, decode whether the token has User_impersonation permission in scp claim and change to Delegated flows for generating token to access backend as I mentioned.