ADAL, Windows Azure AD and Multi-Resource Refresh Tokens
After a ~one-week hiatus, I am back to cover the new features you can find in ADAL .NET.
Today I am going to write about Multi-Resource Refresh Tokens. As I am not a great typist, I am going to abbreviate that in “MRRT”; that does not mean that it’s the official acronym, what I write here is just my personal opinion and does not constitute official guidance, the usual yadda yadda yadda
What is a MRRT?
Simply put: a MRRT is a refresh token that can be used to obtain an access token for a resource that can be different from the resource for which the MRRT was obtained in the first place.
Let’s unpack that concept with one example. Say that I have two Web API projects, resource1 and resource2, both provisioned in the same Windows Azure AD tenant. Say that I have a native client, also provisioned in the same tenant, with the right entries in the permissions table which allow it to call both Web APIs.
If I ask for a token for resource1, I’ll go through whatever authentication flow the operation requires, for example getting prompted via browser dialog, if there’s no existing session. After a successful flow I’ll get back an access token AT1 and a refresh token RT1.
Say that now I want to access resource2. If RT1 is a MRRT, I can simply use RT1 just like I’d use it in the classic refresh flow, but ask for resource2 instead. That will result in getting back an access token AT2 for resource2 (and a RT2 as well) without having to prompt the end user!
This is exceptionally useful. To put things in perspective: a MRRT can play for all the resources in a tenant a role similar to the one played by a TGT in Kerberos. Prompts are reduced to their bare minimum, and you can start to think about sessions it terms that are closer to the ones we are used to on-premises, while at the same time maintaining the flexibility and boundaries-crossing capabilities that OAuth2 affords. Is your mind blown yet?
Let’ See Some Code
This is not just some theoretical mumbo jumbo: you can experience this in your code today (though the endpoint used in the process is still in preview).
Here there’s some code that defines in ADAL terms the scenario described earlier:
// the tenant string authority = "https://login.windows.net/cloudidentity.net"; // the client coordinates string clientId = "a4836f83-0f69-48ed-aa2b-88d0aed69652"; string redirectURI = "https://cloudidentity.net/myWebAPItestclient"; // the IDs of the Web APIs string resource1 = "https://cloudidentity.net/WindowsAzureADWebAPITest"; string resource2 = "https://cloudidentity.net/cisNAPAoidc1"; // the AuthenticationContext representing the tenant in your code AuthenticationContext ac = new AuthenticationContext(authority);
Let’s use ADAL to get a token for accessing resource1:
AuthenticationResult arOriginal =
ac.AcquireToken(resource1, clientId, new Uri(redirectURI));
Assuming that we started from a clean state (empty cache, no cookies for Windows Azure AD) that line of code will cause ADAL to show the browser dialog and the authentication experience. Go through it to completion.
Since I was at it, I took a Fiddler trace to show what happens while AcquireToken runs. Let’s take a look.
That’s quite a lot of stuff for a little line of code!
All the part marked (1) is about getting to render the initial authentication experience in the browser dialog.
The GET in (2) takes place after I type in my username and hit TAB, the page tries to establish whether it should also gather my password or if my tenant has SSO configured and I should be redirected to another endpoint (a local ADFS). My tenant is cloud-only, so I don’t get redirected.
The part in (3) finalizes the user authentication part and results in an authorization code. At this point the ADAL dialog closes down and everything else is handled directly at the HTTP request level.
The part in (4) represents the call to the Windows Azure AD’s Token endpoint, to exchange the code for an access token and associated data(refresh token, expirations, etc).
In fact, I think it’s interesting to take a look at the content of the request to the Token endpoint. Here it is:
POST https://login.windows.net/cloudidentity.net/oauth2/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: login.windows.net
Content-Length: 654
Expect: 100-continue
Connection: Keep-Alive
grant_type=authorization_code&
code=AwABAAA[SNIP]v-YgAA&
client_id=a4836f83-0f69-48ed-aa2b-88d0aed69652&
redirect_uri=https%3A%2F%2Fcloudidentity.net%2FmyWebAPItestclient&
resource=https%3A%2F%2Fcloudidentity.net%2FWindowsAzureADWebAPITest
I edited it a bit for readability. As you can see, that’s a pretty standard code grant request. I have formatted in bold the parts I want you to notice: the fact that we are performing a code grant request, and the fact that we are referring to the resource URI that in our example corresponds to resource1. Those details will become relevant in a moment.
Now let’s take a look at the AuthenticationResult we got back:
We have both an access token and a refresh token, which is great (does not always happen, ADFS will send refresh tokens only under special conditions and ACS never does).
Actually, our refresh token is not a normal one: it’s special! As signaled by the property IsMultipleResourceRefreshToken, what we got back is a MRRT.
The good news is that ADAL is fully aware of how MRRTs work, and can take advantage of those automatically if it has one in its cache.
To see that in action, let’s append a line of code which asks for a token for resource2:
// ... AuthenticationContext ac = new AuthenticationContext(authority); AuthenticationResult arOriginal = ac.AcquireToken(resource1, clientId, new Uri(redirectURI)); // get a token for resource2 right after having gotten one for resource1 AuthenticationResult arViaMultiResourceRefreshToken = ac.AcquireToken(resource2, clientId, new Uri(redirectURI));
Let’s run the code again. You will notice that you get prompted on the first AcquireToken, but not on the second. But that doesn’t prove anything, does it: this behavior might be caused by any number of causes, including the presence of a session cookie (not true, but until I don’t write that post on session I’ve promised I can’t explain more ).
To verify that this was really made possible by the use of a MRRT, let’s get back to Fiddler:
This is the same flow as before, but this time you can see the effect of the second AcquireToken call for resource2: a single request to the Token endpoint. Let’s take a look at its content:
POST https://login.windows.net/cloudidentity.net/oauth2/token HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Host: login.windows.net
Content-Length: 537
Expect: 100-continue
grant_type=refresh_token&
resource=https%3A%2F%2Fcloudidentity.net%2FcisNAPAoidc1&
refresh_token=AwABAA[SNIP]1IAA&
client_id=a4836f83-0f69-48ed-aa2b-88d0aed69652
This time we are using the refresh token, as shown by the grant_type; we are using RT1 (I shortened it for readability but you can see it matches the screenshot of AuthenticationResult) and we are requesting the resource that we mapped to resource2.
If you want to get tokens for other resources provisioned in the same tenant… rinse and repeat!
Applicability
As you have seen, if there is a suitable MRRT in its cache ADAL .NET will take advantage of it automatically. If for some reason you do NOT want this behavior, you can opt out by passing to AcquireToken PromptBehavior.Always (which will force the authentication prompt to show up no matter what) or opt out from using the cache (by passing null at AuthenticationContext construction time). Note that if you opted out from the cache but you still want to take advantage of this feature, you can do so by using AcquireTokenByRefreshToken and passing the target resource.
A refresh token is a MRRT only if IsMultiResourceRefreshToken in the authentication result is set to true.
As of today, only Windows Azure AD can issue MTTR; ADFS in Windows Server 2012 R2 (I really need to find if I can use a shorter name for it ) does not support this, and neither does ACS (which does not support any form of refresh tokens anyway).
Well, there you have it: the MRRT is a super-useful construct, which you are very likely to take advantage of without even knowing it’s there. It will substantially reduce the times in which you need to prompt the end user, shrink traffic and make sessions more manageable. And on that topic, I still want to write a longer post… stay tuned!