The Common Endpoint: Walks Like a Tenant, Talks Like a Tenant… But Is Not a Tenant
The common endpoint is one of the most powerful development features of AAD – unfortunately, it is also one of the least intuitive ones. In this post I will give you a brief taste of what it does, what it is useful for, and how ADAL surfaces its strange properties.
Azure AD Tenant Endpoints
You probably know all this already, but a quick refresher is never a bad thing.
Every Azure AD tenant provides a bunch of endpoints that you can use to secure your applications, choosing between the various protocols AAD supports.
Although those endpoints are all referring to different protocols, they all follow the same basic pattern. In the immortal BNF notation, you could define that pattern as follows:
<protocol-endpoint> ::= <instance><tenant><protocol-specific-path>
<protocol-specific-path> ::= “/oauth2/authorize?api-version=1.0” | “/oauth2/token?api-version=1.0” | “/federationmetadata/2007-06/federationmetadata.xml” | …
<tenant> ::= <tenant-id> | <domain>
<tenant-id> ::= <GUID>
<domain> :: = <hostname>
<instance> ::= “https://login.windows.net/” | …
If you prefer things a tad less formal, we can think of some examples. For instance, if I want to obtain the OAuth2 authorization endpoint of my test tenant developertenant I can simply write:
https://login.windows.net/developertenant.onmicrosoft.com/oauth2/authorize?api-version=1.0
Another way I can refer to the exact same endpoint is the following:
https://login.windows.net/6c3d51dd-f0e5-4959-b4ea-a80c4e36fe5e/oauth2/authorize?api-version=1.0
This is the exact same endpoint – I am simply choosing a different way to identify the corresponding tenant. The advantage of using the domain is mostly that it’s human readable and easier to remember than a GUID. The tenantID, on the other hand, has good properties like immutability (a domain can be discarded, a tenantID is forever), is non-reassignable (discarded domains might get bought by other orgs, messing with your endpoints) and provides a single identifier to construct a stable endpoint no matter how many domains you registered.
Where do I find the tenantID? There are various places to pick it up from. The easy one is via the Azure portal: all endpoints are listed via the view endpoints button on the bottom common bar in the AAD/tenant/applications page. Those endpoints are expressed via the tenantID.
Another way of retrieving that, which I favor because it does not require me to sign in, is to hit the public ws-federation metadata endpoint of the tenant – which is public. You can build that URL using the domain (which I usually remember), but the content of the metadata will always refer the tenantID. For example, if I follow
I’ll land on the following document:
Et voila’, the highlighted text is the tenantID. Also note the entityID, which is in itself a URI parametric in respect to the tenantID: it will come in handy later.
Anyway: once you have your endpoints, you can plug them in your favorite development stack and use them to your heart’s content (usually to request tokens).
Late Binding a Tenant
The above is all fine and dandy if you are writing a line of business app, where the organization you want to authenticate with is known at development time.
However, that does leave out a very large class of important applications: SaaS and multitenant applications. My recent favorite example is Org Navigator, the little app I wrote few weeks ago that allows you to search for users in an Azure AD tenant.
While it sleeps its dreamless sleep in its cell up in the Windows Store cloud, Org Navigator does not know which AAD tenant it will need to search. When you download and install it, Org Navigator still does not know which tenant it should work with. However, when you first launch the app and you try your first query – BAM: it presents you the usual AAD credential experience. You sign in with one user from the tenant you want to target, and you’re in: from that moment on, Org Navigator knows which AAD tenant to query (and knows how to get the tokens needed to do so).
How did I achieve this behavior when developing Org Navigator? What endpoint rendered the AAD credential experience, given that at that point the app did not know which domain or tenantID to use?
If your short term memory is not as bad as mine these days, you already guessed what made this possible: the common endpoint.
The BNF I provided earlier wasn’t really complete: the <tenant> entry should have been
<tenant> ::= <tenant-id> | <domain> | “common”
In a nutshell, common is a convention used to tell AAD “I don’t yet know which tenant should be used. Please render a generic credential gathering experience, and we’ll figure out the tenant depending on what account the user enters”.
That is precisely what I did in Org Navigator. I arranged for the very first authentication operation to go against the authority (in ADAL parlance) https://login.windows.net/common, and that afforded me the behavior described above (for a more detailed walkthrough see Org Navigator’s help page – or download the app and try it on your own tenant or the graph test tenant!).
Other canonical uses can be observed in our multitenant Web samples – in particular, the sign in and sign up links. More details later.
That is pretty handy! The user does the work for you, effectively late binding the tenant. However, there are a couple of things to keep an eye on.
- For line of business applications you do NOT want to late bind the tenant, in fact you want to ensure that the caller comes from your specific tenant and no other! In that case, use of common is not appropriate. More details on the OWIN section below.
- If the user enters the credentials of an account that is a guest in multiple tenants, AAD will not know which one to pick to get the token. As of today, the call will fail.
That said, let’s take a look at some practicalities of using common with web apps (via our OWIN middleware) and via ADAL.
OWIN Middleware and the Common Endpoint
As the post title says, common is NOT a tenant: rather, it is a convention that is used in place of a tenant for driving the real tenant identification process.
Given its placement in the endpoints URI template, however, it is very hard not to think about it as a tenant and just use it everywhere one would use a real AAD tenant. In fact, that’s largely the way in which we use it: however we do need to perform some extra steps to accommodate for common’s peculiar behavior.
Let’s make a practical example. Let’s say that we want to write a multi tenant web app secured via OpenId Connect which can sign users from any tenant. In order to late bind the tenant, we could write the following OWIN auth middleware init:
public void ConfigureAuth(IAppBuilder app) { string ClientId = ConfigurationManager.AppSettings["ida:ClientID"]; string Authority = "https://login.windows.net/common/"; // ... app.UseOpenIdConnectAuthentication( new OpenIdConnectAuthenticationOptions { ClientId = ClientId, Authority = Authority, } // ...
This would indeed cause the desired behavior… almost. If you’d run the app you’d be able to sign in with any user from any tenant, but upon authentication with AAD, the app would reject the call. Why?
Recall how we managed to keep the object model of the OWIN authentication middleware so compact? That’s right, we use the tenant coordinates to read from metadata documents the extra info we need to validate tokens. Now, the common endpoint does expose metadata as well – but they are somewhat incomplete. For example, if I open the OpenId Connect discovery doc I see the following:
Whereas in a real tenant you’d find actual tenantid values, the common endpoint cannot offer that given that the actual tenant that will be used is undefined. The “{tenantid}” string is clearly a placeholder rather than a real value.
The “issuer” value in that doc is the string that the OWIN middleware will use in its default validation logic to validate the “issuer” value of incoming tokens . Clearly that cannot work, given that once the user enters an account from a specific tenant the issuer value will be something like “https://sts.windows.net/6c3d51dd-f0e5-4959-b4ea-a80c4e36fe5e/” which clearly does not match “https://sts.windows.net/{tenantid}/”.
If we’d be working with WS-Federation, the music would be the same. This is common’s ws-fed metadata:
What to do? The only way around this is to override the default issuer validation, which is meant to work with fixed-tenant line of business apps, with your own validation logic.
The OWIN middleware makes it exceedingly easy. For example, in our multitenant web app sample we have a sign up experience which dynamically adds new tenants (or even individuals from arbitrary tenants). There, we turn off the default validation (via TokenValidationParameters/ValidateIssuer=false) and we inject custom logic in the SecurityTokenValidated notification, late in the pipeline because in this case we need to have the user info available. Code here.
If in your heuristic you are only interested in the issuer, you can directly inject your own validation logic in the IssuerValidator notification of the TokenValidationParameters.
Or even further, if for some reason you are not interested in restricting access to your app per tenant you can simply turn off issuer validation and not provide any extra validation logic (if you think you’re in this case, think long and hard about it to ensure that’s truly OK for your scenario to skip that validation!).
That’s pretty much what there is to know about the common endpoint and web apps.
ADAL and the Common Endpoint
The common endpoint is easy to use with ADAL: you just pass it to your AuthenticationContext as the authority, and the authentication experience will follow the behavior described. However common is not a real tenant, and ADAL needs to perform some extra steps here. Namely: once the authentication takes place and a tenant from a real tenant is returned, the AuthenticationContext’s authority is automatically reassigned to that real tenant.
Below you can see an example in which the authority starts as common, but as I sign in with a user from developertenant the authority changes accordingly.
Leaving the authority as “common” would cause all sorts of problems, given that the token so obtained is cached under the real authority: caching under common would not make much sense, given that common can be used multiple times against multiple tenants – which would all end up under the same “authority” and triggering all sorts of weirdness/security issues at cache retrieval time.
Note, this is particularly important for apps that persist their cache across multiple runs. Your AuthenticationContext should use common exclusively when you truly don’t know which tenant to use. That means that on the very first run you can use common to let the user select their tenant of choice, however after that you should track the tenant you find in AuthenticationResult and use it if you happen to create new AuthenticationResults instances afterwards. That is typically the case when you close and re-open an app. Remember: if you don’t do that, you will never hit the cache and your user will always be prompted, every time.
You can choose to save the tenantID in your own store and use it at AuthenticationContext init time. An alternative is to always init AuthenticationContext with common, but once you have the instance in memory to check if the cache does contain already a token for a given tenant (via TokenCache.ReadAllItems +LINQ) – if it does, you can dispose of the common-inited AuthenticationContext and create a new AuthenticationContext which uses the tenant from the cached token; otherwise, you keep the common around so that it will do its late binding magic on the first call. A bit more cumbersome than the case in which you save the tenantID in your own custom location, but it does work as well.
Wrap
The common endpoint is a great feature, I’d daresay indispensible in multitenant scenarios. The way in which it is represented (a parametric tenant) allows you to take advantage of its capabilities using a familiar approach, e.g. treating common as a tenant. There are some differences that eventually you have to handle, however I hope that this post showed they really aren’t rocket science. Let us know if you have feedback or you encounter specific issues, otherwise… happy coding!