Azure AD Application Proxy – SSO and Authorization notes from the field

This blog attempts to capture some of the Single Sign-On and Authorization scenarios I’ve dealt with during my extensive tenure with Azure AD Application proxy deployments.

To keep this blog short there is no description of what Azure AD Application Proxy is generally. More information can be found here

  • AAD SSO Column in the table indicates whether Single Sign-On setting on Azure AD Enterprise Application is enabled or disabled
  • Native Clients in the following table assumes scenario where browser-like features (web view etc) are only used to perform subset of functions, such as authenticating user to Azure AD. All other calls are authorized to Azure AD Application via the usage of Access Tokens passed in Authorization Header
    • For example: The native app may not use cookies to hold session persistence, but rather refresh token stored in the device
    • Some native clients rely completely on running browser inside the app, or open external browser (payment views etc). In these scenarios the application may support similar option as described in the second column (2 Browser)


Back-end Protocol
AAD SSO1 Native Client 2 Browser
SAML – WS/FED (SSO)*Enabled:SAML / or Disabled*No – At least when the most common binding: (Redirect -> POST) is used
Yes 1.Further reading 2.Source
Header Based Authentication against back-end APIDisabledYes With Azure API management using JWT_Bearer Grant Yes With Azure API management using JWT_Bearer Grant (Note this solution is not for rendering browser views. To render browser view from API response additional client side rendering is required
OAuth2 / OIDC Disabled No, unless the application is using web-view that maintains the session to Azure AD (source) Yes – While existing Azure AD session is maintained within browser. Fetch/XHR, or redirect based client can make requests to the back-end app, which can then validate tokens in Authorization Bearer scenario (source)
Header Based Authentication using PingAccessEnabled:Header-BasedNot tested – Probably? (Refer to the documentation) Not tested – Probably? (Refer to the documentation)
Windows integrated Authentication / SPNEGO * * Enabled:Windows Integrated Authentication
Yes – Kerberos Constrained Delegation by the App Proxy Agent is transparent to the client
Yes – Kerberos Constrained Delegation by the App Proxy Agent is transparent to the client
Basic / NTLM Authentication  * * * Disabled No – Authorization header is reserved for Bearer Tokens, which App Proxy Consumes Yes – While existing Azure AD session is maintained within browser, Basic Authentication can be used. NTLM can be used as well, applies also to WIA scenario when WIA fallbacks to NTLM
SOAP API (U/P)Disabled
Yes – When credentials are passed as part of body, and authorization header contains the Bearer Token
Yes – When credentials are passed as part of body, and authorization header contains the Bearer Token
Client CertificateNANo No

Clarifications

  • * Existing IDP Session
    • You could also have specified another Azure AD application separate from the App Proxy Enterprise Application. The SSO works regardless of the SSO setting, when the application uses sames IDP which the user has existing session on.
  • * * About SPNEGO [Source]
    • ”If you configure a connector machine for SPNEGO, make sure that all other connectors in that Connector group are also configured with SPNEGO. Applications expecting standard KCD should be routed through other connectors that are not configured for SPNEGO ”
  • * * * Compound Authorization (in some cases Double authentication)
    • First Authorization: Azure AD Consumes the Authorization header
      • While Sending additional Bearer token in other than Authorization header, or in body payload
      • Or While Sending additional Credentials in other than Authorization header, or in body payload
      • Sequentially (after) challenged by the back-end app running in browser session

Good to know

  • Native API clients using AAD Pre-authentication
    • With Native Clients Forwarding JWT tokens to back-end service doesn’t work unless the Native client is calling Azure AD App Proxy from webview like representation or pure browser
  • Only user identities can passed through application having Azure AD pre-authentication enabled
    • For example Application identity (Client Credentials Flow) doesn’t work with Azure AD App Proxy Applications, unless Compound Authorization is used
    • Apps can still work in chain (On-Behalf-of-Flow for example) as long as the identity being delegated is user identity
  • If you have Windows Authentication enabled in the Application, ensure the deployment scenario supports Kerberos Constrained Delegation, and the Azure AD App Proxy agent can act as delegating party to the SPN.
  • While Mutual TLS authentication isn’t supported features such as require device compliance from and other Client recognizing options are available when combined with Conditional Access.
  • There are also other scenarios which I may cover in another blog post
    • Using a mix of pre-authenticated and pass-trough Azure AD Applications
    • Linked applications
    • Password-based

Further reading

https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/application-proxy-configure-single-sign-on-with-kcd

https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/application-proxy-configure-for-claims-aware-applications

If you find any discrepancies (errors etc) with the information on the table just dm me at @santasalojoosua, or post question to this blog, and I shall correct it /explain it.

Experimental testing: Azure AD Application Proxy With Azure Application Gateway WAF

Disclaimer: This configuration example is only for experimental testing. I’d advise against using it in any kind of serious scenario as the configuration has no official support …and is based on-whim testing 🙂

I was recently browsing Feedback for Azure AD Application Proxy, and noticed that I am not the only one who would like to see WAF functionality enabled for AAD App Proxy.

The comment for ”Under Review” raised my curiosity ” We are reviewing options for creating smoother integration and providing documentation on how to layer the two. ”
https://feedback.azure.com/forums/169401-azure-active-directory/suggestions/31964980-allow-azure-ad-app-proxy-apps-to-use-the-azure-web

While its fairly easy to retrofit WAF API -scenario with Azure AD App Proxy and API management, it’s another thing to also make it render web pages in a browser without a custom front end. https://securecloud.blog/2019/06/01/concept-publish-on-prem-api-using-aad-app-proxy-and-api-management-with-azure-ad-jwt-bearer-grant/

Test configuration

Application Proxy Configuration

Application Gateway Configuration

  1. Create Listener binding the cert for App Proxy Apps FQDN

2. Add the IP of Azure AD App Proxy as back-end target

  • The logic: Point the DNS to Application Gateway instead to App Proxy Application, and point the application gateway to that CNAME, and override the naming bind in the listener of Application Gateway
Use the name AppProxy DNS should be pointed at

3. Override the host name to the same name that is in the DNS (this would create loop, unless we hadn’t different name in the back-end pool)

Now watch the back end for traffic originating through WAF + AppProxy

Back-end application receiving WAF forwarded traffic, with both App Proxy and Application Gateway headers
  • Obvious problem is that the attacker can bypass WAF by ”gatewaying” itself with custom DNS directly to the AppProxy.
    • Obviously there is no public reference anywhere, what is the IP for Azure AD App Proxy app, or whats the name of the app, as the communication goes through App GW, and DNS points to App GW. Depending on the back-end app, the attacker might figure out a simple way, to get the app ”echoing” back the route (For example headers…)
  • Sub optimal mitigations would be (if back-end app is configurable, and you want to check in back end that did the request come from WAF)
      • that the only calls that have last X-Forwarded-For IP as Application Gateway would be authorized.
      • Or to set an ”secret” header in Application Gateway URL rewrite rules, and check the presence of that header in the back-end app for authorization
  • If I had to do this in production today, I would place WAF in the internal network before the back-end app
AppGW WAF headers
The app is the consent extractor, which i just used as placeholder app (has no context meaning in this scenario)

I will stay tuned to see if this feature gets actually implemented!

Br Joosua!

Azure AD App Proxy|Forward incoming JWT token to backend service: What are my choices?

Currently there is feature request in feedback.azure.com for getting JWT Tokens forwarded into the back end.

https://feedback.azure.com/forums/169401-azure-active-directory/suggestions/32386468-forward-incoming-jwt-token-to-backend-service

There are at least two scenarios for such request. I am taking some shortcuts here, and assuming that in most scenarios this is an access token, similar to the one that is issued and sent with Native Clients in Authorization: Bearer … header

  • Browser clients using XHR / Fetch
    • This seems to work ”out-of-the-box” as the browser session is ”authenticated” with session data held in the session persisting cookies
    • I am using an example where the back-end service supplies the client per client side chained fetch() requests with any Access Token. This token is then sent back to back-end and displayed in the back end. This is to prove, that Azure AD application Proxy doesn’t strip the bearer token form Authorization header
function getToken () {
    fetch('/refreshToken').then((response) => {
        response.json().then( (data) => {
        console.log(data['access_token'])
        var token = data['access_token']
        fetch('/caller',{
            headers: {
                'Authorization': 'Bearer ' + token
                // 'Content-Type': 'application/x-www-form-urlencoded',
              },
        }).then( (response) => {
        response.json().then((data2) => {
            console.log(data2)
                })
            })
        })
    })
}
Authorization header is contained, and can thus be received in the back end
  • Native clients outside of web view sending the Access Token destined for AppProxy itself
    • I am excluding native client using web view like scenario where a browser is ”conjured” in the app. In which case I’d assume that web view would behave similarly as the browser example (mostly?) and successfully send the token to the back-end
    • This doesn’t work (And per explanation in the feature request, that’s by design), but alternative ways are available, which I’ve previously explored in another post
There is no Authorization Header. I’ve added extra header for (Authorization2) for illustrative purposes

Concept: Publish on-prem API using AAD App Proxy and API Management with Azure AD JWT Bearer Grant

Disclaimer: Azure AD App Proxy is perfectly capable of covering most of the internal API publishing scenarios, If you can handle API request and response handling with just client and on-premises server. Alternatively you might have another component on-prem which can act as middle-tier component to do further validation and shaping of requests. In a nutshell, if you’re API-scenario doesn’t benefit from from middle-tier service, then I suggest you continue with ”keeping-it-simple” /And as always, all Disclaimer: The information in this weblog is provided “AS IS” with no warranties and confers no rights.

Better together?

API Management and AAD App Proxy can complement each other when you need to have request shaping / central API Gateway for processing before calling back-end API’s – In this blog I explore a PoC example, and some reasoning for such scenarios

When to use? / benefits

  • You’re internal API isn’t visible to Azure API management via on-premises network connectivity, and you’re not planning to use site-site networking in the future, or for a particular API
  • You want to enrich payloads and headers of requests for particular back-end services. For example services which cant consume claims in JWT Tokens. You also want to ensure, that selected parts of these payloads cannot be forged by the client
  • Have single end-point to distribute and shape/manipulate traffic to various API’s (General argument)
Extra Claims from APIM

How it works (short)

  • APIM calls the App Proxy SPN instead of mobile clients
  • The ServicePrincipals for APIM and AppProxy App are blocked for providing access tokens via Authorization Code Grant (removal of redirect uri). Leaving only option that APIM will use the JWT-Bearer grant. This ensures, that only the APIM can fetch the ”final” access token, for the App Proxy App
  • Flow: Native client (Auth Code Flow) -> APIM (JWT-Bearer Grant) -> Azure AD App Proxy SPN Authorization (Permissions to make this work are explained on later part of this blog)

Ensuring integrity with retrofit of AAD App Proxy & APIM

  • In order to retrofit the Azure AD Application Proxy with APIM it’s essential that the App Proxy Application and APIM SPN can act only as as Web API’s (not public clients) this keeps the flow intact
    • Stripping token issuance rights of the SPN for Authorization Code flow, ensures: That only the App registration for APIM can delegate user access to the App Proxy SPN (Audience) using that particular flow
    • This assumption only works when you don’t retroactively enable Implicit Flow on the SPN itself. (You can have Implicit grant on other clients, but not on the this particular SPN, which is the owner of the AppProxy Audience ( identifierUri)

How To?

  • Remove redirect URI’s from both middle-stream API and App Proxy Application
  • Then perform the fencing (below) by delegating rights in correct order to support the flow
  • Now the public client, can only get tokens for APIM, but can never call App Proxy directly, as the client doesn’t have direct permissions on the App Proxy SPN (Only the APIM has)

Azure AD Fencing: Utilizing extended grant types to access downstream API’s

Click the picture for bigger version
OAuth2.0 On-Behalf-Of flow
https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow#protocol-diagram

Using Azure AD Specific GrantType (urn:ietf:params:oauth:grant-type:jwt-bearer) is the magic component we use here.

  • JWT bearer flow allows us to create ”DMZ-like” fencing between direct calls, and downstream calls destined AppProxy SPN with Middle-tier API

Using the On-Behalf-Of flow (JWT bearer), we can ensure that APIM is the only allowed caller for the App Proxy Audience

Click the picture for bigger version

API Management configuration

  • The following policy is ”tip of the ice berg” in terms of how you can shape, and handle requests bound to multiple directions
  • There is possibility of doing more graceful handling with more the multiple policy clauses APIM provides
  • I can hardly claim any credit (apart architectural and flow design) for the APIM policies below, as web is full great APIM examples for all of the policies I have used below
 
<policies>
    <inbound>
        <!-- validate the initial call destined later towards middle-tier API-->
        <validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Access token is missing or invalid.">
            <openid-config url="https://login.microsoftonline.com/dewired.onmicrosoft.com/.well-known/openid-configuration" />
            <audiences>
                <audience>https://webapi-a.dewi.red</audience>
            </audiences>
            <required-claims>
                <claim name="appid" match="any">
                    <value>299568d2-3036-41d9-a961-89266e67ea82</value>
                </claim>
            </required-claims>
        </validate-jwt>
        <!-- Forward THE UPN header to back-end -->
        <set-variable name="UPN" value="@(context.Request.Headers["Authorization"].First().Split(' ')[1].AsJwt()?.Claims["upn"].FirstOrDefault())" />
        <set-variable name="Bearer" value="@(context.Request.Headers["Authorization"].First().Split(' ')[1])" />
        <set-header name="back-endUPN" exists-action="override">
            <value>@(context.Variables.GetValueOrDefault<string>("UPN"))</value>
        </set-header>
        <!-- Send new request with the Token -->
        <send-request mode="new" response-variable-name="OBOtoken" timeout="20" ignore-error="false">
            <set-url>{{tokenURL2}}</set-url>
            <set-method>POST</set-method>
            <set-header name="Content-Type" exists-action="override">
                <value>application/x-www-form-urlencoded</value>
            </set-header>
            <set-header name="User-Agent" exists-action="override">
                <value>Mozilla/5.0 (Windows NT; Windows NT 10.0; fi-FI) WindowsPowerShell/5.1.17763.503</value>
            </set-header>
            <set-body>@{
            var tokens = context.Variables.GetValueOrDefault<string>("Bearer");
           
              return "assertion=" + tokens + @"&client_id={{clientid2}}&resource={{resource}}&client_secret={{ClientSecret}}&grant_type={{grantType}}&requested_token_use={{requested_token_use}}";
             
               }</set-body>
        </send-request>
        <!-- Forward the OBOtoken to AppProxy  -->
        <choose>
            <when condition="@(((IResponse)context.Variables["OBOtoken"]).StatusCode == 200)">
                <set-variable name="OBOBearer" value="@(((IResponse)context.Variables["OBOtoken"]).Body.As<JObject>(preserveContent: true).GetValue("access_token").ToString())" />
                <set-variable name="Debug" value="@(((IResponse)context.Variables["OBOtoken"]).Body.As<JObject>(preserveContent: true).ToString())" />
                <set-header name="Authorization" exists-action="override">
                    <value>@{
                    var ForwardToken = context.Variables.GetValueOrDefault<string>("OBOBearer");
                    return "Bearer "+ ForwardToken;
                    }</value>
                </set-header>
                <set-header name="Content-Type" exists-action="override">
                    <value>application/json</value>
                </set-header>
            </when>
        </choose>
        <base />
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies> 

Testing the solution in action

Before testing ensure that all places where you’ve defined audiences are explicitly matching for the audience (App registrations, Named Values, APIM policies, Clients requesting the access)

There are multiple ways to test the solution, but testing through APIM’s test console, and peeking the in-secure back-end resource via HTTP trace yields the most verbose results

  • Get Access Token for the API-A (APIM) with ”bulk client”
  • Paste the access token to APIM test console and perform test call to view traces
  • Check the back-end for results
Click for bigger picture

And that’s it!

Further stuff

  • Use keyVault instead of named values (secret) for storing secrets
named values
  • Place WAF to the front of APIM
  • fine tune to the policies in APIM ( this was just the PoC)
    • For example, the back-end could use cached token for the downstream call, as the user, and user-identity is validated in the first step (its validated in the second step also)
https://docs.microsoft.com/en-us/azure/api-management/api-management-howto-cache
  • Get some proficiency in C# syntax … As PS and JavaScript fellow, I found myself seriously struggling to properly escape, cast and enumerate variables/content

P.S. My similar articles