Blog Post

Microsoft Developer Community Blog
3 MIN READ

Introducing a better way to integrate Azure AD with API Management

adrian_hall's avatar
adrian_hall
Icon for Microsoft rankMicrosoft
Nov 18, 2022
Since, well, the beginning of Azure API Management, you've been able to validate that the Json Web Token (JWT) coming into your Azure API Management service is valid before passing it onto the backend service. The validate-jwt policy is one of the most used policies within Azure API Management, will happily ensure your client applications are using the right client IDs, and have the right audiences and claims. Meanwhile, Azure Active Directory (AAD) is a mainstay of enterprise APIs, providing authentication and authorization controls for a wide variety of APIs from M365 APIs to custom-built APIs.  It's generally the center piece of your enterprise API security infrastructure.

 

Wouldn't it be wonderful if they worked better together.
 

Introducing validate-azure-ad-token policy

This week we introduced a new policy for working with AAD in Azure API Management - the validate-azure-ad-token policy. It's a direct replacement for validate-jwt that you can use when you are targeting AAD.

 

Here is the general form of the policy:
<validate-azure-ad-token tenant-id="{{aad-tenant}}">
    <client-application-ids>
        <application-id>{{aad-android-client-id}}</application-id>
        <application-id>{{add-ios-client-id}}</application-id>
        <application-id>{{aad-web-client-id}}</application-id>
    </client-application-ids>
    <audiences>
        <audience>@(context.Request.OriginalUrl.Host)</audience>
    </audiences>
    <required-claims>
        <claim name="ctry" match="any">
            <value>US</value>
        </claim>
    </required-claims>
</validate-azure-ad-token>
 
This version ensures that the audience is the API Management host and that the optional claim ctry (the country) is provided and has a specific value. This is actually a more complex example than is necessary.  You can get minimal validation by just specifying the `tenant-id` and the client application-id values you expect:
<validate-azure-ad-token tenant-id="{{aad-tenant}}">
    <client-application-ids>
        <application-id>{{aad-client-id}}</application-id>
    </client-application-ids>
</validate-azure-ad-token>

 

More information about the specifics of the policy can be found in the documentation.

 

Since we know that Azure API Management works wonderfully with AAD, it makes sense that we make it easier to configure and easier to take advantage of value-added services provided by the AAD service. Features like continuous access evaluation improve your security posture by removing the lag between when a token is issued and when it can be revoked. This allows you to issue tokens for longer periods without a loss in security which, in turn, improves the performance of the client application. You can expect to see these features being added to our new validate-azure-ad-token policy in the future.

 

We're really excited by this new policy because it provides an anchor for AAD specific functionality in the future.  It's easier to configure and sets you up for adopting future security enhancements at the gateway.  This will allow your API service to adopt the security enhancements provided by AAD without any code changes.

 

The validate-azure-ad-token policy is recommended for protecting your API with Azure Active Directory identities and Azure API Management.
Updated Nov 21, 2022
Version 3.0

19 Comments

  • Rezaal860's avatar
    Rezaal860
    Copper Contributor

    adrian_hall we tried the policy fragment route , but soon hit the wall because multi-valued claim check simply doesnt work in the new policy validate-azure-ad-token. To be precise, this wont work:

     

     

    <fragment>
    	<validate-azure-ad-token tenant-id="contoso.onmicrosoft.com" failed-validation-error-message="Invalid Access token ">
    		<client-application-ids>
    			<application-id>ABC</application-id>
    		</client-application-ids>
    		<audiences>
    			<audience>api://XYZ</audience>
    		</audiences>
        <required-claims>
          <claim name="roles" match="all" separator=",">
                    <!-- When i set a variable outside of policy fragment and send it here with the exact same claims, I get a 401 error -->
                    <value>Claim1,Claim2,Claim3</value>
            </claim>
        </required-claims>
     
    	</validate-azure-ad-token>
    </fragment>

     

    This is how I reference the fragment policy:

     

     <set-variable name="Claims" value="Claim1,Claim2,Claim3" />
            <include-fragment fragment-id="PolicyFragment-ClaimsValidation" />

     

  • Hi Rezaal860, given the way that Azure AD scopes and permissions work, there should be no problem doing a generic check at the product level, and then either checking the decoded JWT during the API and Operation or doing another check for the specific claims.  Policy fragments will allow you to set this sort of thing up easily.

  • Rezaal860's avatar
    Rezaal860
    Copper Contributor

    adrian_hall  Great works. Thanks for this new policy. Quick question why did you guys decide to make client ID and audience, mandatory? What if somebody wants to check for different attributes at different levels. For example, at the API level I would like to do something like below policy and it doesn't let me do that (I have already validated client Ids and Audience at the Product Policy level) 

     

    <validate-azure-ad-token tenant-id="contoso.onmicrosoft.com" failed-validation-error-message="Invalid claims.">
    <openid-config url="https://login.microsoftonline.com/contoso.onmicrosoft.com/v2.0/.well-known/openid-configuration" />
    <claims-to-validate>
    <claim name="roles" match="USA" />
    </claims-to-validate>
    <failed-validation-response>
    <set-status code="401" reason="Unauthorized" />
    <return-response>
    {
    "error": "The provided JWT token does not contain the required claim."
    }
    </return-response>
    </failed-validation-response>
    </validate-azure-ad-token>

     

     

    This forced me to use the old validate-jwt policy to check for the existence of the claims I want to check which works just fine but obviously I want to use the new policy there too  without having to repeat what I already checked at the Product level:

     

    <validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Invalid calims">
    <openid-config url="https://login.microsoftonline.com/contoso.onmicrosoft.com/.well-known/openid-configuration" />
    <required-claims>
    <claim name="roles" match="all">
    <value>USA</value>
    </claim>
    </required-claims>
    </validate-jwt>

     

    How do we do chaining of token validation using this new policy?

  • Hi markweatherill - Managed System Identities are just regular identities, which are connected to an app registration for permissions.  As long as you know what the app registration is, you can put that into the client-application-id field.  If you've got multiple services connecting to API Management, you can list multiple client-application-id fields.

  • Hi trevorjones - the library we are using checks against both the v1 and v2 tokens.  Without delving further into the specifics (which I can't do here), I can't say why it isn't working in your specific case.  I suggest you open a support ticket for this situation.

  • trevorjones's avatar
    trevorjones
    Brass Contributor

    adrian_hall If it knows who the issuer is, why is it returning an error? Is this not a standard authority for Azure AD?

    https://login.microsoftonline.com/{{tenant-id}}/v2.0

    At the moment it is impossible for me to use this new policy since I have no way of knowing what issuing authorities the policy will accept, and apparently the v2.0 authority I'm using is not accepted and there is no way for me to provide it. I'll have to stick with validate-jwt.

  • markweatherill's avatar
    markweatherill
    Copper Contributor

    Can this policy be used with clients which are connecting via Managed Identity?

     

    I currently use the approach described here: https://notetoself.tech/2021/04/05/calling-api-management-from-azure-function-using-managed-identities/

    i.e., Managed Identities are assigned a role against the application protecting the backend API.

    # Assign the managed identity access to the app role. 
    New-AzureADServiceAppRoleAssignment -ObjectId $managedIdentityObjectId -Id $appRoleId -PrincipalId $managedIdentityObjectId -ResourceId $serverServicePrincipalObjectId

     

    The roles claim is then checked by validate-jwt policy:

    <validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Access token is missing or invalid." require-scheme="Bearer">
         <openid-config url="https://login.microsoftonline.com/{{tenantID}}/v2.0/.well-known/openid-configuration" />
         <audiences>
             <audience>{{clientID}}</audience>
         </audiences>
         <issuers>
             <issuer>https://sts.windows.net/{{tenantID}}/</issuer>
         </issuers>
         <required-claims>
             <claim name="roles" match="any">
                 <value>{{roleId}}</value>
             </claim>
         </required-claims>
    </validate-jwt> 

     

    It's not clear how the client-application-ids should work with Managed Identity.

  • Given that it is Azure AD and you are providing the tenant ID, we know who the issuer is, so you don't need to provide it.

  • trevorjones's avatar
    trevorjones
    Brass Contributor

    Is there a reason this new policy doesn't support the <issuers> element, as validate-jwt does? Without it, I get "invalid issuer" on v2.0 tokens:

     

    Simple policy:

    <validate-azure-ad-token tenant-id="{{tenant-id}}" header-name="token">
        <client-application-ids>
            <application-id>{{app-id}}</application-id>
        </client-application-ids>
    </validate-azure-ad-token>

    With validate-jwt, I would have to specify the issuer like so:

    <issuers>
        <issuer>https://login.microsoftonline.com/{{tenant-id}}/v2.0</issuer>
    </issuers>