Using Fiddler to simulate AAD failures with ADAL.js
Authentication can be hard. Getting your error handling logic right can be especially hard if you cannot easily reproduce the error conditions that one needs to handle. This article provides some background on the Implicit Grant flow with Azure Active Directory (AAD) as supported by ADAL.js, then shows how to use Fiddler to simulate various error conditions.
Scope of this article
Microsoft offers a set of libraries for dealing with identity in the cloud with AAD (Azure Active Directory), supporting various authentication flows such as OAuth 2 Authentication Code or OAuth 2 Implicit Grant flows. This article is meant exclusively for the latter (Implicit Grant flow), where a web application obtains access tokens directly from AAD without requiring intermediate servers to process any responses. In this case, we are using the ADAL.js library.
This article is not going to talk about how to use or configure ADAL.js. There are several other sources of information about those. Instead, my focus will be on how to test your application for some of the common AAD errors you or your users might encounter to make sure that you can properly detect and react to them.
Background
The Implicit Grant flow is commonly used for authentication in modern Single Page Web Applications (SPA). Microsoft Teams and Microsoft PowerApps Studio for Web are examples of products that use AAD Implicit Grant for authentication at the time of this writing.
Access tokens are obtained by pointing a browser window (or iframe) to an AAD endpoint and specifying details of the request as query parameters. Under the right circumstances, AAD will respond with an HTTP 302 (Redirect) status code, causing the window (or iframe) to navigate to the specified redirect_uri and the desired access token will be available in the fragment (that is, after a '#' character in the destination uri).
Example flow:
To obtain an access token, ADAL.js creates an iframe pointed at:
https://login.microsoftonline.com/common/oauth2/authorize?response_type=token&client_id=12345678-90ab-cdef-1234-567890abcdef&resource=https%3A%2F%2Fsome.resource.example.com%2F&redirect_uri=https%3A%2F%2Fmysite.example.com%2Fauthframe&state=fedcba98-7654-3210-fedc-ba9876543210%7Chttps%3A%2F%2Fsome.resource.example.com%2F
On success, AAD responds as follows:
HTTP/1.1 302 Redirect Location: https://mysite.example.com/authframe#access_token=eyJmYWtlIjp0cnVlfQ.eyJmYWtlIjp0cnVlfQ.eyJmYWtlIjp0cnVlfQ&token_type=Bearer&expires_in=3599&state=fedcba98-7654-3210-fedc-ba9876543210%7Chttps%3A%2F%2Fsome.resource.example.com%2F
When things go wrong
Several things can go wrong in this flow. Some causes are due to code or configuration defects. You, as the developer, are supposed to fix any such issues yourself, hopefully before your users encounter them. Other failures, however, can happen at run-time depending on user access policies and browser settings. A robust web application must be able to detect and react to these. Here are some examples of responses you should expect.
If the user is not signed in (e.g. cleared browser cookies), AAD might respond as follows:
HTTP/1.1 302 Redirect Location: https://mysite.example.com/authframe#error=login_required&error_description=AADSTS50058%3a+A+silent+sign-in+request+was+sent+but+no+user+is+signed+in.+The+cookies+used+to+represent+the+user%27s+session+were+not+sent+in+the+request+to+Azure+AD.+This+can+happen+if+the+user+is+using+Internet+Explorer+or+Edge%2c+and+the+web+app+sending+the+silent+sign-in+request+is+in+different+IE+security+zone+than+the+Azure+AD+endpoint+(login.microsoftonline.com).%0d%0aTrace+ID%3a+00112233-4455-6677-8899-aabbccddeeff%0d%0aCorrelation+ID%3a+ffeeddcc-bbaa-9988-7766-554433221100%0d%0aTimestamp%3a+2017-07-07+04%3a30%3a00Z&state=fedcba98-7654-3210-fedc-ba9876543210%7Chttps%3A%2F%2Fsome.resource.example.com%2F
If the desired resource requires multi-factor auth (MFA), but the user has not gone through MFA yet, AAD might respond as follows:
HTTP/1.1 302 Redirect Location: https://mysite.example.com/authframe#error=interaction_required&error_description=AADSTS50076%3A+Due+to+a+configuration+change+made+by+your+administrator%2C+or+because+you+moved+to+a+new+location%2C+you+must+use+multi-factor+authentication+to+access+'01122334-4556-6778-899a-abbccddeeff0'.%0D%0ATrace+ID%3A+00112233-4455-6677-8899-aabbccddeeff%0D%0ACorrelation+ID%3A+ffeeddcc-bbaa-9988-7766-554433221100%0D%0ATimestamp%3A+2017-07-07+04%3A30%3A00Z&state=fedcba98-7654-3210-fedc-ba9876543210%7Chttps%3A%2F%2Fsome.resource.example.com%2F
If you specify a redirect_uri that is not registered with AAD, AAD will refuse to follow it, so you will not get the usual HTTP 302 (Redirect) response. Instead, AAD will return 200 (OK) with HTML content that would show an error to the user, including a human-readable error message that indicates you have an invalid redirect_uri. In ADAL.js, errors such as this are usually identified as a timeout (i.e., ADAL.js was waiting for the page to be redirected to the redirect_uri, but this doesn't happen and after a few seconds ADAL.js gives up). Look for the constant
LOADFRAME_TIMEOUT
in the ADAL.js source code for more info.
Simulating failures with Fiddler
If you paid attention to the examples above, you may have noticed that each of the responses includes the same state
value as was passed on the request from the browser. It is critical to make the simulated response behave the same way. We will leverage Fiddler's FiddlerScript for this.
Ensure you have a recent version of Fiddler installed and that it is configured to decrypt HTTPS traffic.
Note: There are security implications to doing this. Make sure you are comfortable with the potential risks involved, some of which are described here.
On the right panel, select the FiddlerScript tab
Find the
OnBeforeRequest
method definition and add the following at the end of the method body:if (1 && oSession.oRequest.host == "login.microsoftonline.com" && oSession.HTTPMethodIs("GET")) { var url = oSession.fullUrl; var end = url.Length; var qmark = url.IndexOf("?"); var qparams = {}; if (qmark >= 0) { var query = url.Substring(qmark + 1, end - qmark - 2); qparams = Utilities.ParseQueryString(query); } if (qparams["response_type"] === "token") { oSession["ui-color"] = "purple"; // Visually indicate this request was modified oSession["ui-backcolor"] = "cyan"; oSession.utilCreateResponseAndBypassServer(); var newLocation = qparams["redirect_uri"] + "#error=login_required&error_description=Simulated+aad+error&state=" + Utilities.UrlEncode(qparams["state"]); oSession.responseCode = 302; oSession.oResponse.headers.Add("Location", newLocation); } }
To easily enable or disable this code, simply change
if (1 &&
on the first line toif (0 &&
.When Fiddler is capturing, any web applications that attempts to acquire tokens using AAD's Implicit Grant Flow will now experience the simulated error. You can easily add additional checks to only intercept requests meant for your AAD app client_id. The variable
qparams
above is your friend.
Wrapping up
That's it for now. I hope this helps some people out there test their applications and ensure they can handle these kinds of errors gracefully. Let me know if you have comments or suggestions and I will try to address them. See you next time!