Azure AD Developer Tips and Tricks – Part 3
AKA The "OAuth Tips from a Dummy" Edition
Raise your hand if you have ever heard of OAuth. Ok, I can see a lot of hands there. Now, raise your hand if you can list all the different flows and use cases of OAuth. Hmm…Less hands it seems…
Let me just say right at the start of this post that I am not an OAuth guru. I have not read all the protocol documents. I would not be able to say "hey, there's a bug in your implementation" if I had the source code of Azure AD in front of me. I have attended training sessions with smart people though, so I know enough to be dangerous :)
There is good reference material on the web, and there are good video trainings, and everything. It's not a bad idea to go through some of that material. Thing is that it's not necessarily motivating, it's still hard, and it might still be that you're slightly confused as to what to do with it. And it easily takes an hour or two to go through it as well.
This post will be about OAuth, but I am not going to show you everything you wanted to know about OAuth or an "ultimate guide". I'm not turning this into "all the protocol details in twenty simple lessons" either. I'm taking a slightly different approach, and trying to teach you as little as possible while still making things work.
Remember how I said in part 1 that Azure AD handles a lot of the low level hassle for you? Do you think you need to know the cryptographic details of how the token is signed to access the Graph API? No. Microsoft wrote the pieces that issue the token for you. Do you know how to accept a token, and why token acquisition is different in a native app vs a web app? That would be more useful to know, because those pieces are not all taken care of by Microsoft.
What about SAML, WS-Fed, blah blah? Well, I did use "tip" in the title, so here's one: Don't.
Ok, don't take this as a bashing of other protocols, but unless someone has a specific requirement that can only be solved using SAML, (because some legacy enterprise platform which stopped getting updates three years ago only speaks SAML), new code should in most cases be written with OAuth and OpenID Connect in mind. One thing is the implementation of it, but setting up federations and such later on isn't fun either. If you're a ninja in those other protocols you have however possibly learned more about OAuth than I will be covering already, or have the capacity to learn it easily, so you should be able to retarget your knowledge accordingly.
AuthN & AuthZ
Starting with the very basics - why are we even talking about OAuth in the first place? Well, why are you using Azure AD in the first place? The purpose of Azure AD is authenticating users and apps, and authorizing access to resources. That could be said to be the high level goal. OAuth (and OpenID Connect) are the low level parts of how it actually happens.
Pro tip: Authentication (AuthN) is about answering the question "who are you?". Authorization (AuthZ) follows, and is about answering the question "should you be allowed to access this resource?".
OAuth vs OpenID Connect
This is a point that causes some confusion so I'll share a few lines about it before going further. When OAuth came about it addressed some specific use cases, but was not designed (nor intended) to solve all authentication scenarios. The scenario where you use an external Identity Provider (IdP) to have users interactively sign in to your application wasn't really covered. This led to different approaches being implemented that all had their own nuances. Want to sign in with Google instead of Microsoft (on basis of both supporting OAuth)? Just need to change a couple of lines of code, and add some extra lines if you need to support both at the same time. They were mostly similar, but not identical. Which is kind of a problem... This eventually led to a standardization effort to actually make this work without rewriting code.
OpenID Connect builds on top of OAuth though, and is not meant to replace it. For a non-interactive server to server use case it's still perfectly valid to not implement OpenID Connect.
Pro tip: For an interactive login by an end-user OpenID Connect is usually what you want. For a server type non-interactive login OAuth is what you want.
Developers are used to thinking about tiers and layers. The component driving the user interface is separated from the component driving the SQL database in the background. Authentication works in a similar way. The "Login" button visible to a user does not trigger the same code as a server side process needing to look up all users in a given office location. The verbiage of the protocols do not use this term, but for having a discussion everyone can take part in I use the term "app types" for describing these different ways of driving the auth.
With native app we mean something the end-user installs. This could be an app for an iPhone or an Android mobile device, but also a Win32 client for a Windows box. This is considered an untrusted client.
This is an odd case where there is a client installed, and it authenticates as a user although not necessarily having an interactive element. Think a service running in the background doing things, either as a server side component, or part of the client install on the end user device. This could be considered both a trusted and an untrusted client depending on the specific implementation scenario.
Pro tip: if an end user has direct access to the executable the client is considered untrusted - aka unable to keep secrets.
What about APIs, aren't they a type of app as well? They are an important part of the picture yes, and it is of course very common to build applications where most of the functionality is provided by an API having only a thin piece of user interface on top exposing things to a user. But keep in mind that the API is usually not the component doing the login. The API mainly receives a token and verifies whether these are valid keys to the kingdom. The UI should contain the code needed to acquire said token.
Single-Page Apps (SPAs)
Whether you are using Azure AD as the Identity Provider, or some other product, you usually need to start with registering an entry for your app. You can't display you driver's license when you' re caught speeding unless you have gone through the trouble of acquiring a license first :) (Hey, another awful analogy!)
This is also the part where you need to have an idea of the app type you're building because the wizards will require you to make some choices. (And Microsoft are keeping the protocol level details out of it at this stage. Which makes sense since registering an app does not lock you down to using OAuth; other protocols are also available, and they sometimes employ the same basic mechanisms.)
As you can see they are fairly similar, with only minor differences, so they should all be possible to get through. The settings that opens themselves up afterwards are not included here though, for the sake of not complicating matters.
And now we get to the meatier parts. In OAuth parlance the different mechanisms for logging in are called "flows". Why not app types or something similar? Well, while there are natural ways to map the mechanisms to different type of apps there is nothing technically preventing you from doing things that aren't necessarily recommended. The OAuth backend does not care about this, and does not force you into doing things a certain way, so a more generic term is more suitable.
I'm only covering the basics here though. You can build out complex scenarios where a user interactively signs in, calls an API which in turn calls a different API on behalf of the user. Or using claims to branch differently depending on where you sign in from. (A good example of this done right is the Multi-Factor Auth feature in Azure AD where you can skip MFA while on the corporate network, but trigger a second factor if outside this boundary.) I don't know if I'll cover those flows later on, but they certainly don't belong in the "minimum OAuth knowledge needed" that I'm trying to present here.
This flow is very similar to the traditional username/password login mechanism. You have a clientId, and a clientSecret which for most purposes behave similarly except the secret is usually something you would not be able to memorize at first try. These credentials are not intended for being used by actual end users however, and rather identifies the app itself. While you can have multiple secrets per app, there is only the one id, and it is in no way scalable to provide unique identities per actual end user of an app. This flow is intended for trusted clients, and you should never ever store the secret outside a server context! The secrets can be changed, and rolled over on an interval, but updating this in a bunch of installations is a major hassle.
Use this flow for server type apps. Client Credentials logically maps to the Web App in the Azure portals.
This is a very common use case, and unfortunately the one that is hardest to grasp at first. The client "signals" that it would like to sign in, and identifies itself with the clientId of the app. This in turn triggers a redirect to a web view hosted by Azure AD where the user types in their username and password, and then the user is sent back to your app with a token. The important part here is that the app never has access to the actual credentials of the user, while at the same time the authentication process is tied to the app so you can't randomly invoke this process from a third-party app. (Clearly there's more low level details to it than you might get the impression of from my super-short description.)
Use this flow for end-user interactive apps. Authorization Code logically maps to the Native App in the Azure portals.
This flow is used when you need to identify as a user, and not an app, but want to do it in a non-interactive way. Think something like the user typing in credentials during install, and then have the app use this in the background for subsequent logins.
This flow is suitable for service/daemon type of apps. It doesn't map directly to an app type in the Azure portals, but there are restrictions enforced in the background so it might not work for all use cases.
There are scenarios where you possibly need both a web page and a native app. There's nothing preventing you from registering multiple apps, and using several clientIds. Maybe you want a different app for iOS and Android - go ahead. Maybe you have a web app where you don't need to use client credentials. You can mix and match these things as you like - there is no one-to-one mapping between app types and flows so you can be flexible.
Pro tip: Follow these general designations, and think twice before implementing "clever" variants of these.
A common "clever implementation" that I hear often enough to want to highlight here is abusing the password grant flow. Frequently there is a request to be able to modify the user experience for signing in; either because one doesn't like the layout of the default Microsoft-branded look, or reducing the number of steps involved. And then you're thinking "hey, if I go with the password grant I can have the user type in their credentials in a UI I control 100%, and do the actual authentication in the background". Yes, technically you can do this. And you should not do it... The whole point of driving the user to Azure AD is the security aspect of you not taking care of this in your app. If you implement this by handling the user's credentials in clear text you introduce a whole new bundle of security issues. If I downloaded a third-party app implementing this flow I would uninstall it faster than you could say "this app will make me rich".
Does this mean it is never ok to provide a customized login experience? Well, there are different levels of customization of the Azure AD login. You can change the branding and texts quite easily, and with Azure AD B2C work is being made to provide richer customization. There could be times when the password grant mechanism can be "acceptable" - for instance in a non-public app installed only on approved corporate computers for internal users. For users outside your own realm; just say no to such tricks.
Sign-on URL, redirection URI - what does it all mean?
When you go through the wizards for registering an app these will possibly mean nothing to you, but you'll usually be able to plot in some value that is accepted allowing you to finish the wizard. Unfortunately it could lead to an app that isn't going to actually work afterwards (even if you do most things correct in the code).
Since the OAuth flows are started outside Azure AD, and the result also ends up outside Azure AD, AAD needs to keep track of two important things. Where are the requests coming from, and where is the intended recipient located. Failure to check these parameters can easily lead to possible security issues.
Sign-on means which URL I'm coming from, for instance https://www.contoso.com/login. It should always be an encrypted connection, and http:// is not an option. (Yes, this means you will need to go through acquisition of certificates when deploying. If you deploy to Azure App Services a default one protecting *.azurewebsites.net is always included with no extra cost.) This is mostly a Web app thing, so the Native app option doesn't ask for this.
Redirect/reply URI is the place where the token will be passed along after the user has validated. Exactly how the OAuth server facilitates this with multiple valid URLs, etc. varies, but it needs to be registered. If you just provided it as a parameter when invoking the login process a malicious app could potentially trigger an innocent-looking login popup only to intercept the token afterwards and do nefarious things. To reduce the risk, (after all with physical access you can do a lot of bad things on any device), this is not ok. For a web app you could pass it to the root or a "/authenticated" part of the site. It doesn't have to start with https:// though. You can have platform-specific URIs, or for that matter something like contoso:// if your app platform supports it. (iOS, Android and Windows 10 all do.) This parameter is relevant both for native apps and web apps.
But here's a part that might not be apparent from the registration. This really only applies to authorization code flows, (of the flows we have mentioned). Client Credentials, and Password Grant will not do the redirect dance - they just do a straight up query and get the token back directly. Which also means that if these credentials fall in the wrong hands a lot of damage can be done before you realize what's going on.
Sure, we're still light on the code here. This is still by design, but to stave off the worst withdrawal symptoms I want to lead you in the direction of some code I have available to demo these flows.
Head to the following GitHub Repo:
There's different scenarios shown there, but for the basics check out the following (link to guide in parentheses):
AboutMePasswordGrant - Password Grant Flow (https://aadguide.azurewebsites.net/integration/aboutmesample/)
GraphTreeView - Client Credentials Flow (https://aadguide.azurewebsites.net/integration/graphtreeview/)
HelloAzureAD - Authorization Code Flow (https://aadguide.azurewebsites.net/integration/helloazuread/)
There is a mix of libraries used for the OAuth flows, so it can be a bit confusing since I haven't touched upon that. (Until we get there - yes, there are considerations as to whether you want to use helper libraries or not.)
I don't know if this very reduced OAuth walkthrough makes sense, or if it just came off as incoherent rambling and not entirely helpful. Maybe this column is turning into a never-ending story with the need for more and more parts to get through everything - we will see :)