Using ADAL .NET to Authenticate Users via Username/Password
This might be the most requested feature for ADAL: the ability of authenticating a user by pumping in username/password, without showing any pop up. There are perfectly legitimate scenarios that require that feature; unfortunately there are also many ways in which abusing this feature might backfire.
With the RC we just released, we added this feature to ADAL .NET (but not to Windows Store or Windows Phone). We have a sample showing how to use it; here I’ll highlight the minimal syntax that lights it up, discuss some considerations about different cases, and above all I’ll spell out limitations and warnings about what you are bargaining when you choose to go this route.
When to Use This Feature
There are a number of scenarios where the direct use of username and password is inevitable. The ones below are the ones I encountered most often.
Note that all of those scenarios could be potentially solved by Windows Integrated Auth (WIA), however not all setups can leverage that (e.g. cloud-only tenants, clients running outside of the domain, etc) hence in the below I assume we’re in such unfortunate cases.
Headless clients
Say that you are operating from a console app, running on a Server Core. There is simply no Windows manager on the box to render any UX – everything needs to take place in text.
Legacy Solutions
During the Ask the Expert night at this year’s TechEd North America I met with a gentleman who described a setup in which he was using legacy hardware already sending username and password. His investment in such clients was massive and decomissioning them (or the software running on them) was out of question. He very much wanted to move to AAD and move his backend to the cloud, but could not rely on our current credential gathering experience. The direct use of username and password will allow him to bridge his legacy solution to his new cloud based backend, secured via AAD.
Automated Testing
This is an all-time favorite of our partners within Microsoft. If I’s have a dollar for every mail/IM/hallway conversation I got about this…
The scenario is simple: you have a solution based on native clients and you want to automate its verification. Existing test harnesses don’t always make it easy to automate a web based credential gathering interface, hence the request for a mechanism to easily obtain tokens in exchange for test credentials.
When NOT to Use This Feature
This is easy: in pretty much any other case! Direct manipulation of credentials is a BIG responsibility that significantly grows your attack surface, is conducive of bad habits (like caching the credentials), denies you pretty much all of the advantages you get by presenting a server-driven experience (multi factor auth, consent, multi-hop federation, etc – see below) and makes your client deployments brittle.
The main anti-pattern hidden behind requests for this feature is the desire for customizing the authentication experience. I totally understand that desire, but I often get the impression that the tradeoff one makes when going that route are not always well understood. Falling back to direct credential manipulation is an awfully high price to pay: it cuts you our from a long list of features and puts both your users and your app at risk. I would rather hear your feedback about what parts of the server-provided UX you want to customize – and fiercely fight for you in shiproom to make that change happen – rather than helping you through a security crisis.
How it works
Enough with the doom & gloom, let’s take a look at some code!
For the visitors form the future: this feature lights up for the first time in ADAL version 2.7.10707.1513-rc.
I’d daresay that the way in which this feature has been implemented fits right in in the existing, well-proven credentials model we introduced since v1 for handling client credentials flow (see this sample).
We introduced a new type, UserCredential, which represents a user credential. If you want to use username and password, you’d initialize a new instance via the following:
UserCredential uc = new UserCredential(user, password);
Where user is a string containing the UPN of the user you want to authenticate, and password is a string or a SecureString containing… well, you know.
How to you use uc for getting a token? Well, we added a couple of overloads to the AcquireToken* family:
public AuthenticationResult AcquireToken(string resource, string clientId, UserCredential userCredential); public Task<AuthenticationResult> AcquireTokenAsync(string resource, string clientId, UserCredential userCredential);
The relationship between the client app and the resource is precisely the same one you learned about in all other single tenant native client->web service ADAL samples: both need to be registered, the API needs to expose at least a permission, the client needs to be configured to request such permission, and so on. Note that ehre there is no opportunity for AAD to prompt for consent, hence flows which would require it are off limits here.
Once you call one of those overloads, as long as you provided the correct credentials (and your tenant is configured correctly) you’ll get back a standard AuthenticationResult, the resulting tokens will be automatically cached, and so on.
You can get a feeling of how this all works by giving a spin to our headless native client sample on GitHub. Here there’s a screenshot of a typical run, to give you a feeling of the experience you can achieve with this flow. Party like it’s ‘95!
To peek a bit behind the scenes, there are two main sub-scenarios:
- your user belongs to a managed tenant. In that case, ADAL performs an OAuth2 password grant and gets back the usual result.
- your user belongs to a federated tenant. In that case, ADAL discovers the coordinates of the corresponding ADFS instance, hits it via WS-Trust, sends the resulting SAML token to AAD as an assertion and gets back the usual result.
From your code you won’t notice any difference between the two cases – I am just mentioning that so that you’re aware of what’s required for making this flow work. For example, if instead of ADFS you set up another IP that does not expose WS-Trust endpoints or does it differently from ADFS, this flow will likely fail.
Constraints & Limitations
Here there’s a list of limitations you’ll have to take into account when using this flow.
Only on .NET
Given the intended usage of this feature, we decided to add it only to .NET.
On Windows Store we added the ability to use Windows Integrated auth, which has many of the same advantages and less drawbacks. Details in another post.
No web sites/confidential clients
This is not an ADAL limitation, but an AAD setting. You can only use those flows from a native client. A confidential client, such as a web site, cannot use direct user credentials.
No MSA
Microsoft accounts that are used in the context of an AAD tenant (classic example: Azure admins) cannot authenticate to AAD via raw credentials – they MUST use the interactive flow (though the PromptBehavior.Never flag remains an option).
No MFA
Multi-factor authentication requires dynamic UX to be served on the fly – that clearly cannot happen in this flow.
No Consent
Users do not have any opportunity of providing consent if username & password are passed directly.
No multi-hop federation
Any scenario requiring home realm discovery, multiple federation hops and similar won’t work – the protocol steps are rigidly codified in the client library, with no chance for the server to dynamically influence the authentication path.
No any server side features, really
In the “traditional” AcquireToken flows you have the opportunity of injecting extra parameters that will influence the behavior of AAD – including parameters that AAD didn’t even support when the library was released. None of that is an option when using username and password directly.
Next
Direct use of username an password is a powerful feature, which enables important scenarios. However it also a bit of a Faustian pact – the price you pay for its directness is in the many limitations it entails and the reduced flexibility that it imposes on any solution relying on it.
If you are in doubt on whether this feature is right for your scenario, feel free to drop us a line!