The use of Azure AD Behind “Deploy to Azure”
About one week ago I got a mail from my good friend Brady, who was looking for some clarifications about our Azure AD multitenant web app sample. That piqued my curiosity: Brady doesn’t stay still for one sec and we chat about identity all the time, but multitenancy?
As it turns out, he was cooking a really awesome project: a way of fast-tracking deployments of GitHub repos to new or existing Azure web sites. As of this morning, he release the project’s bits and blogged about it.
You should really head to Brady’s blog and check out the details of the project. In this post I’ll add some color to its identity backstory.
Deploy to Azure and Azure AD’s Multitenancy Features
The idea behind Deploy to Azure is beautifully simple.
Say that you have the source of a great web app in a GitHub repo, and that you want to make extra easy for anybody to deploy your web app in their own Azure web site.
Deploy to Azure provides you with a simple button which will do just that, all you need to do is adding it to your repo. The button is in fact just an affordance for following a deep link, which points to a web application.
The web app will start by asking you to authenticate with your Azure AD credentials – the ones corresponding to your Azure subscription (more about this later). If you happen to be already authenticated, you’ll skip this step and find yourself single-signed-on in the app right away.
The app will ask you some basic questions about your deployment, such as the name of the Azure web site you want to target. Done that, by clicking the “Deploy” button you’ll trigger the web site creation (if necessary) and subsequent deployment. Straight from a public GitHub repository to an Azure web site of your choosing!
Pretty neat, right? Once again, I encourage you to visit Brady’s blog to learn abut all the MAML (Microsoft Azure Management Libraries) magic that powers this sample.
That said, let’s see what makes Deploy to Azure tick from the identity perspective.
Signing In
The web app that makes all this possible is a fork of our Azure AD multitenant web app sample.
Deploy to Azure is significantly simpler than the original sample, given that it only manipulates 1st party resources – that is, Microsoft Azure management API. The original sample shows you how to protect your own resources, hence it contains code that is necessary for representing those resources and restricting access only to known users. In Deploy to Azure the authorization logic is applied automatically – Azure already knows what users can deploy to web sites, hence we dispensed of all the onboarding logic and associated custom validations.
The sign in flow is enabled by the brand new ASP.NET OWIN OpenId Connect (OIDC) middleware. If you navigate to App_start/startup.auth.cs, you’ll locate right away the crucial bits carrying the sign on configuration:
app.UseOpenIdConnectAuthentication( new OpenIdConnectAuthenticationOptions { ClientId = clientId, Authority = Authority, TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters { // instead of using the default validation (validating against a single issuer value, as we do in line of business apps), // we inject our own multitenant validation logic ValidateIssuer = false, }, // ...other stuff
Here there’s the interesting bit about multitenancy.
In the simplest case, apps configured to use Azure AD are set up to work with ONE specific directory tenant. That’s the typical line of business app scenario: a contoso.com developer creates one app and wants to make it available to all his/her contoso.com colleagues. In that case, the value of Authority would be “https://login.windows.net/contoso.com” and the OIDC middleware would make sure that only users presenting tokens issued by contoso.com’s Azure AD tenant would be accepted.
That would be too restrictive for Deploy to Azure: here we want to be able to accept anybody who has a valid Azure AD account, no matter from which directory he/she comes from. We relax the default issuer validation policy via that TokenValidationParameter init you saw in the snippet above: not checking for any particular issuer means that we will be accepting tokens from any Azure AD tenant.
That solves the validation part, but we still need to do something about the Authority.
Azure AD exposes a special endpoint, referred to as “common”, of the form “https://login.windows.net/common”. “Common” makes it possible to gather user credentials without having to decide in advance from which Azure AD tenant he/she comes from. All you need to do is using the common endpoint as Authority, and the app is configured to accept tokens from any Azure AD user without the need for you to understand anything of the following explanations. I am adding those in case you really want to understand what’s going on in there.
I described the common endpoint in details in this other post. Here I’ll offer a super quick explanation of how the mechanism works, in part because that’s useful knowledge if you often write multitenant apps against AAD and in part because it will make it easier later in the post to explain why Deploy to Azure currently suffers from an important limitation.
Here there’s a rough diagram of how the common endpoint sign in works. I am assuming that the user is not already signed in – you can easily derive the behavior in that case by considering that steps 1 to 3 would take place automatically, without showing any UX to the user.
1 – The user clicks on the Deploy to Azure button. That triggers a link that leads to a protected action on the Deploy to Azure web app, which in turns generate a web sign in request via the common endpoint.
2 – Azure AD serves back a generic credential gathering experience.
3 – As the user types the username, Azure AD infers the actual Azure AD tenant from which the user is from. Depending on the nature of the tenant, this might affect the experience: for example, if contoso is what we call a federated tenant (that is to say, its directory is a cloud projection of an on-premises one) then the auth flow would be directed to the local contoso ADFS instance.
4 – With the tenant correctly identified, the user is authenticated and the requested token is issued by the target tenant. That token is what the OWIN middleware was waiting for to declare the caller signed in.
Note that, the first time this flow takes place, the user will be informed about what resources and permissions Deploy to Azure is requesting: actual issuance of the token wil depend on whether the user grants or deny consent. I didn’t depict this part for simplicity.
That’s pretty neat! As mentioned earlier, if your app were to façade your own resources you might want to add extra checks on from which tenant the user is coming from – see the original sample for that – but for Deploy to Azure that’s all we need to do for web sign on.
Obtaining a Token for Accessing the Azure Management API
The web sign on is only the beginning. Deploy to Azure needs to be able to invoke the Azure management API. In order to do so, it needs to acquire an access token with the right permissions.
OpenId Connect offers a great hybrid flow that non only signs the user in a web application, but at the same time it also obtains an authorization code that can be redeemed for access and refresh tokens for a resource of your choice.
You can see the code redemption logic in action in the AuthorizationCodeReceived notification, once again in startup.auth.cs. ADAL makes it really easy, hiding all of the underlying protocol complexity and saving the tokens in a distributed cache that can later be accessed from anywhere in the web application. The initial access and refresh tokens obtained are for the Graph API, while Deploy to Azure needs tokens for the Azure management API: however, thanks to the fact that all refresh tokens issued by Azure AD today are multi-resource refresh tokens, having those initial tokens in the cache allows Deploy to Azure to silently (e.g. without user prompts) obtain all the other tokens it needs. You can observe that principle in action in the DeployController, where the Index action taps onto the common token cache to get the tokens it needs for performing its Azure management magic. For details about that, see Brady’s post.
Known Limitations & Workarounds
Deploy to Azure is a super early preview, as Brady mentions. Here I’d like to dig a bit in a shortcoming that you are pretty likely to notice: in its current form, Deploy to Azure will not work with a MSA (formerly known as LiveID) account, even if that MSA is a global administrator of your Azure AD and subscription.
The root cause of this comes from the use of the common endpoint and the fact that today knowing the MSA of a user is not enough to infer which Azure AD tenant should issue the requested token. Below you can find a version of the earlier sign on diagram, modified to show what happens when you use an MSA.
1 – The user clicks on the Deploy to Azure button. That triggers a link that leads to a protected action on the Deploy to Azure web app, which in turns generate a web sign in request via the common endpoint.
2 – Azure AD serves back a generic credential gathering experience.
3 – As the user types the username, Azure AD infers that the user comes from MSA and redirects accordingly. Here the suer successfully authenticates.
4 – The flow goes back to AAD, carrying a token proving that the user successfully authenticated with MSA. However, that does not help Azure AD to decide which tenant should issue the token to Deploy to Azure! AN MSA user is not tied to any specific AAD tenant, and in fact can be a guest in many (as depicted). Currently Azure AD does not know how to eliminate the ambiguity, and fails to issue a token.
The main workaround available today entails avoiding the use of common. That can be achieved in different ways:
A – if you want to offer the feature to your own organization, or any known organization, you can fork the code and use the specific organization identifier in lieu of common. Once that happens, you can safely use guest MSAs as there is no doubt on which tenant should issue the token.
B – this is a bit more elaborate. Instead of automatically triggering authentication upon accessing Deploy to Azure, you could offer one experience that asks the user for the tenant (in form of its domain) that they want to use. You could then inject that back in the Authority, basically getting back to A. You might even create a tracking cookie to pick up that choice automatically the next time around.
I hope that the issue will be fixed, but hopefully those workarounds will get you going if you want to kick the tires with the app!
Next Steps
Working with Brady is always a lot of fun! I am excited to see Deploy to Azure making use of Azure AD and ADAL features to enable such compelling scenario, and I can’t wait to see how the project will evolve with your contributions