Authenticating to Azure AD non-interactively

fingerprint-1382652_640I want to use Azure AD as a user directory but I do not want to use its native web authentication mechanism which requires users to go via an Active Directory page to login (which can be branded and customized to look like my own).

I just want to give a user name & password to an authentication API.

The reason is I want to migrate an existing application currently using an LDAP server and want to change the code as little as possible.

You might have other reasons why you want to do that.

Let’s just say that the online literature HEAVILY leans towards using the web workflow.  Even the documentation on native apps recommends that your native app pops the Azure AD web page to authenticate the user.  There are good reasons for that as this way your app never touches user credentials and is therefore more secure and your app more trustworthy.  So in general, I totally agree with those recommendations.

But in my case, my legacy app is touching the user credentials all over the place.  Also this approach is a stepping stone before considering moving to the web workflow.

After a weekend spent scanning the entire web (as a side note I’ve learned there was little Scandinavian men living inside the Earth, which is flat by the way), I finally found Danny Strockis’ article about Authenticating to Azure AD non-interactively using a username & password.

That is it basically.  Except the article is a little quick on setup, so I’m gona elaborate here.

I’m going to give a sample in C# using ADAL, but since at the end of the day, the authentication is one HTTP POST, I’ll also give a more bare bone sample using HTTP post if you don’t want or cannot to integrate with ADAL.  The samples are quite trivial so you should be able to convert them in the language / platform of your choice.

Conceptually

We basically want our users to interact with our application only, punch in their credentials and have the application check with Azure AD if the credentials are good.

image

Here the application is represented as a Web app here but it could also be a native application.

In order to do this in the world of Azure AD is to use two Azure AD apps:

  1. A client app, representing the agent authenticating the user
  2. A service app, representing the actual application the user is authenticating against

I know it looks weird, it makes your brain hurts and is in great part the reason I’m writing this article, because it isn’t straightforward.

image

In the authentication parlance, the client App is the client (so far so good) while the service app is the resource, i.e. the thing the user is trying to access:  the client is accessing the resource on the user’s behalf.

Setting up Azure AD

Yes, there will be some steps to setup Azure AD.

First we need a tenant.  We can use the tenant used by our subscription but typically for those types of scenario we’ll want to have a separate tenant for our end users.  This article shows how to create an Azure AD tenant.

We then need a user to test.  We can create a user like this:

image

where, of course, ldapvpldemo is my Azure AD tenant.

We’ll also need to give that user a “permanent” password, so let’s show the password on creation (or reset it afterwards) then let’s go to an InPrivate browsing window and navigate to https://login.microsoftonline.com/.  We can then login as the user (e.g. test@ldapvpldemo.onmicrosoft.com) with the said password.  We’ll be prompted to change it (e.g. My$uperComplexPassw0rd).

Let’s create the client app.  In App Registrations, let’s add an app with:

image

It would probably work as a Web app / API but a Native App seems more fitting.

We’ll need the Application ID of that application which we can find by looking at the properties of the application.

image

We then need to create the service app:

image

We’ll need the App ID URI of the service:

image

That URI can be changed, either way we need the final value.

We will need to give permission to the client app to access the service app.  For that we need to go back to the client app, go to the Required Permissions menu and add a permission.  From there, in the search box we can just start to write the name of the service app (e.g. MyLegacyService) and it should appear where we can select it.  We then click the Access MyLegacyService box.

Finally, we need to grant the permissions to users.

image

With all that, we’re ready to authenticate.

ADAL

This sample is in C# / .NET but since ADAL is available on multiple platform (e.g. Java), this should be easy to port.

We’ll create a Console Application project.  We need the full .NET Framework as the .NET core version of ADAL doesn’t have the UserPasswordCredential class we’re gona be using.

We need to install the NuGet package Microsoft.IdentityModel.Clients.ActiveDirectory in the project.

[code language=”csharp”] private static async Task AdalAuthenticationAsync() { // Constants var tenant = "LdapVplDemo.onmicrosoft.com"; var serviceUri = "https://LdapVplDemo.onmicrosoft.com/d0f883f6-1c32-4a14-a436-0a995a19c39b"; var clientID = "b9faf13d-9258-4142-9a5a-bb9f2f335c2d"; var userName = $"test@{tenant}"; var password = "My$uperComplexPassw0rd1";

        //  Ceremony
        var authority = "https://login.microsoftonline.com/" + tenant;
        var authContext = new AuthenticationContext(authority);
        var credentials = new UserPasswordCredential(userName, password);
        var authResult = await authContext.AcquireTokenAsync(serviceUri, clientID, credentials);
    } [/code]

We have to make sure we’ve copied the constants in the constant section.

UPDATE (06-09-2017):  The name of the constants match the ADAL SDK but doesn’t always match what we see on the portal screens.  Here are the constants mapping.  The tenant is the name of your AAD tenant appended by .onmicrosoft.com.  The serviceUri is the App ID URI we collected above (red box).  The clientID is the Application ID we’ve collected above (red box).  Finally, user name and password belong to the actual user we want to authenticate.

This should work and authResult should contain a valid access token that we could use as a bearer token in different scenarios.

If we pass a wrong password or wrong user name, we should obtain an error as expected.

HTTP POST

We can use Fiddler or other HTTP sniffing tool to see what ADAL did for us.  It is easy enough to replicate.

[code language=”csharp”] private static async Task HttpAuthenticationAsync() { // Constants var tenant = "LdapVplDemo.onmicrosoft.com"; var serviceUri = "https://LdapVplDemo.onmicrosoft.com/d0f883f6-1c32-4a14-a436-0a995a19c39b"; var clientID = "b9faf13d-9258-4142-9a5a-bb9f2f335c2d"; var userName = $"test@{tenant}"; var password = "My$uperComplexPassw0rd";

        using (var webClient = new WebClient())
        {
            var requestParameters = new NameValueCollection();

            requestParameters.Add("resource", serviceUri);
            requestParameters.Add("client_id", clientID);
            requestParameters.Add("grant_type", "password");
            requestParameters.Add("username", userName);
            requestParameters.Add("password", password);
            requestParameters.Add("scope", "openid");

            var url = $"https://login.microsoftonline.com/{tenant}/oauth2/token";
            var responsebytes = await webClient.UploadValuesTaskAsync(url, "POST", requestParameters);
            var responsebody = Encoding.UTF8.GetString(responsebytes);
        }
    } [/code]

Basically, we have an HTTP post where all the previous argument are passed in the POST body.

If we pass a wrong password or wrong user name, we should obtain an error as expected.  Interestingly, the HTTP code is 400 (i.e. bad request) instead of some unauthorized variant.

Summary

Authenticating on an Azure AD tenant isn’t the most recommended method as it means your application is handling credentials whereas the preferred method delegate to an Azure AD hosted page the handling of those credential so your application only see an access token.

But for a legacy migration for instance, it makes sense.  Azure AD definitely is more secure than an LDAP server sitting on a VM.

We’ve seen two ways to perform the authentication.  Under the hood they end up being the same.  One is using the ADAL library while the other uses bare bone HTTP POST.

Keep in mind, ADAL does perform token caching.  If you plan to use it in production, you’ll want to configure the cache properly not to get strange behaviours.


40 responses

  1. Anonymous 2017-07-06 at 19:11

    This is one of the simplest solution. Most of my web services just need to verify our employees credentials.

  2. Vincent-Philippe Lauzon 2017-07-07 at 10:11

    The usual approach for Web Services is to accept an AAD token in the HTTP header of the request. This is standard behaviour and can be enforced by configuration in the case of Azure Web App for instance (see https://vincentlauzon.com/2016/03/11/securing-rest-api-using-azure-active-directory/).

    This way not every Web Service call has purview on your employee’s credentials.

  3. Vincent Oh 2017-07-24 at 19:49

    Hi Vincent,

    I tried following your guide and called the API using postman with a new user id/pw. Unfortunately i got back this response:

    { “error”: “invalid_grant”, “error_description”: “AADSTS65001: The user or administrator has not consented to use the application with ID ‘81eb88c7-f693-44cc-aedc-5d964d1afac8’. Send an interactive authorization request for this user and resource.\r\nTrace ID: 3fe69df1-1a9e-4251-a921-7eb43dcb3500\r\nCorrelation ID: 3529f119-2fb1-4d13-9144-2d4da296722a\r\nTimestamp: 2017-07-25 02:46:08Z”, “error_codes”: [ 65001 ], “timestamp”: “2017-07-25 02:46:08Z”, “trace_id”: “3fe69df1-1a9e-4251-a921-7eb43dcb3500”, “correlation_id”: “3529f119-2fb1-4d13-9144-2d4da296722a” }

    Following that, i tried using my default username & pw but i got this response. { “error”: “invalid_request”, “error_description”: “AADSTS90002: The requested namespace does not exist.\r\nTrace ID: 3fe69df1-1a9e-4251-a921-7eb452b03500\r\nCorrelation ID: e1d79317-6cbd-49d6-bfdf-307fd4ba0134\r\nTimestamp: 2017-07-25 02:40:17Z”, “error_codes”: [ 90002 ], “timestamp”: “2017-07-25 02:40:17Z”, “trace_id”: “3fe69df1-1a9e-4251-a921-7eb452b03500”, “correlation_id”: “e1d79317-6cbd-49d6-bfdf-307fd4ba0134” }

    Could you advise on this please? Your guide have been really helpful and is exactly what i wanted. Thanks.

  4. Vincent-Philippe Lauzon 2017-07-31 at 14:34

    It sounds like you didn’t “grant the permissions” (search for those terms in this page) on your Legacy Service.

  5. Ogheneogaga Agi 2017-08-08 at 21:49

    Please is there a working code example…?

  6. Vincent-Philippe Lauzon 2017-08-30 at 14:04

    There is code in the article and it is working… can you be more specific?

  7. Gene 2017-09-05 at 17:51

    Hello Vincent, The code calls I understand. But from where did the assigned values come from, for the variables: tenant , serviceUri , clientID? I know what TenantId is, and what ApplicationId is (in this Azure AD context). But ‘tenant’ is not TenantId, and I do not know that ‘clientID’ is? For ‘serviceUri’, I expect to use http://manager.azure.com/ , does that seem sane?

    Thanks for the C# code example, by the way.

  8. Vincent-Philippe Lauzon 2017-09-06 at 14:18

    Hi Gene,

    Good feedback. I’ve added an update below the ADAL code snippet to explain where those constants come from.

    Hopefully that answers your questions.

  9. Zaki Saad 2017-09-24 at 22:25

    Amazing, I have been having so many issues integrating the M$ portal into a secure enterprise web app - could not thank you more for this!

  10. Vincent-Philippe Lauzon 2017-09-25 at 06:04

    Glad to hear that!

  11. harish 2017-09-29 at 10:57

    Thanks much for the article , bumped across this after trying several other failed attempts.

  12. Siddharth Dhawan 2017-10-12 at 00:50

    Hey, can i tried to use the same approach to authenticate an AD user to a web app. I got the following exception: “Failed to acquire token silently as no token was found in the cache”

  13. Vincent-Philippe Lauzon 2017-10-12 at 10:58

    You should be able to. There might be something wrong with your configuration as it seems to require a pre-existing token in the cache and it shouldn’t.

  14. Noor Elahi 2017-10-16 at 05:59

    Line 59: var credentials = new Microsoft.IdentityModel.Clients.ActiveDirectory.UserCredential(userName, password); Line 60: //var credentials = new UserPasswordCredential(userName, password); Line 61: var authResult = await authContext.AcquireTokenAsync(serviceUri, clientID, credentials);

    [AdalServiceException: AADSTS70002: The request body must contain the following parameter: ‘client_secret or client_assertion’.

  15. Vincent-Philippe Lauzon 2017-10-17 at 14:32

    No idea of my own… But check out http://www.matvelloso.com/2015/01/30/troubleshooting-common-azure-active-directory-errors/ and search for the error message. Someone gives the guidance of a trailer slash…

  16. Joshua@MSFT 2017-10-18 at 18:13

    Great write up! The only thing I’d add is if you do this in a static method, you’ll need to call:

    authContext.TokenCache.Clear();

    right after instantiating the AuthorizationContext.

  17. Kostadin Mandulov 2017-10-30 at 04:14

    Ran into the same error following the example to the letter.

  18. Justin 2017-12-06 at 08:09

    I am very new to async tasks so any help is much appreciated. I am never able to obtain the results of the async task you show above as I always seem to end up with “An asynchronous module or handler completed while an asynchronous operation was still pending.” What is the correct way to obtain the results of the access token in the method you provide?

  19. Vincent-Philippe Lauzon 2017-12-06 at 10:16

    I think you have a problem with the environment. Take a look at the like of https://stackoverflow.com/questions/15060214/web-api-httpclient-an-asynchronous-module-or-handler-completed-while-an-async.

  20. Justin 2017-12-06 at 10:45

    I was able to get past the first issue but now I always get this error:

    {“error”:”invalid_client”,”error_description”:”AADSTS70002: The request body must contain the following parameter: ‘client_secret or client_assertion’.\r\nTrace ID: 5c2f3d11-97ca-4280-83ea-8e9c634c7000\r\nCorrelation ID: f18a2418-1f30-4ca2-8ead-000237b049ab\r\nTimestamp: 2017-12-06 18:43:01Z”,”error_codes”:[70002],”timestamp”:”2017-12-06 18:43:01Z”,”trace_id”:”5c2f3d11-97ca-4280-83ea-8e9c634c7000”,”correlation_id”:”f18a2418-1f30-4ca2-8ead-000237b049ab”}

  21. Justin 2017-12-06 at 11:06

    I actually fixed the last issue as well. Now I just need to figure out how to extract the accessToken in the response body to pass as a bearer token.

  22. RS 2018-01-02 at 04:09

    A good article Vincent. I understand the concept now. I am trying to follow it but facing the below issue: I created a console app (.net core) as my VS 2017 community edition only shows this project option for console app. I then installed the following ADAL package from NuGet - Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory -Version 3.17.3

    In the article you have mentioned that we need the full .net version of ADAL. Could you please elaborate on this? From where do I get this? As when i cannot access the UserPasswordCredential Class in my console app code whereas it exists in v3.17.3 or ADAL.

    Please ignore my lack of knowledge on this..it’s been a while since i last did some coding.

    Please let me know.

  23. Vincent-Philippe Lauzon 2018-01-02 at 04:15

    Hi RS,

    This article is one year old and that constraint might very well have been lifted. If you made it work, I wouldn’t worry about that.

  24. RS 2018-01-02 at 04:20

    Hi Vincent,

    Thanks for your reply. What i meant was that i cannot access the UserPasswordCredential class. So i as stuck. Any ideas what i can try to resolve this issue? Thanks

  25. Vincent-Philippe Lauzon 2018-01-02 at 05:00

    I do not know. My guess is there are three avenues:

    1- that API took a different shape in the .NET core SDK 2- that API isn’t supported (yet?) in .NET core SDK 3- that API is deprecated and isn’t supported in new SDKs

    If option 1 doesn’t pan out, i.e. you can’t find anything in the SDK, you could try to do it in .NET fx, capture the HTTP POST generated by the SDK and simply write custom HTTP-request code in .NET Core to handle the authentication. The issue you’ll then have is how to insert the token you got so you can continue the flow with ADAL…

    Have you tried hitting the developer forums with this question? It would be interesting to get an answer from the product owners.

  26. RS 2018-01-02 at 15:27

    Hi Vincent,

    Could you please tell me which specific version of ADAL you have used in this example?

    Thanks

  27. Toke Fogh 2018-03-16 at 05:29

    Hi Vincent.

    What route do you recomend now that UserPasswordCredential war removed from the NuGet package?

  28. Vincent-Philippe Lauzon 2018-03-17 at 04:34

    I haven’t looked into it.

    It likely still is available in the REST API, but if it disappeared from the SDK, it is likely that the API is going to be removed in time.

  29. Orlando 2018-06-09 at 02:27

    Real superb information can be found on site.

  30. Ranjith Nanjala 2018-10-29 at 11:49

    Does this POST mothod works for .net Core. I am using .Net Core 2.0. I know we can directly give office 365 portal URL and redirect the user. However, we have some internal issues to use that route, instead I am trying to query against the Azure AD by sending the credentials.

  31. Raju Chapagain 2019-02-05 at 16:58

    Hi Vincent, Your article was helpful to understand the concept. I am looking to a single sign to some third party application through our web application. The provider suggested to go through Azure AD single sign-on, but I couldn’t find any relevant resources. Could you advise if you have any sample?

  32. Vincent-Philippe Lauzon 2019-02-06 at 05:13

    Hi Raju,

    Do you mean using Azure AD for authenticating users on your web site? Or using Azure AD to broker different third parties authentication (e.g. Facebook, LinkedIn, etc.)?

  33. Sree Chi 2019-08-01 at 14:54

    Very nice article. How did you get the “APP ID URI? “https://LdapVplDemo.onmicrosoft.com/d0f883f6-1c32-4a14-a436-0a995a19c39b”; would like to know and figure out, what this might be in our scenario?

  34. Sree Chi 2019-08-02 at 10:21

    Hi Vincent, Can you help in replacing LDAP with ADAL.net like what you explained here? Do i need to register two apps in the Azure AD? I thought i need to create a native app and i am not sure on Service app and APP ID uri. Can you please let me know in figuring out the “APP ID URI” ?

  35. Sree Chi 2019-08-02 at 10:59

    Hi Vincent, I am trying to replace LDAP in a web application. Created the client as “public client” app and created service app as “web”. No more i see “WebApp /API” and “Native”. Instead listed as “Web” and “public client”. But not sure what to give for “APP ID URI” for the service app. is that the web application login URL?

  36. Anonymous 2019-09-05 at 14:14

    Hi Justin, How did you fix the following error: ”AADSTS70002: The request body must contain the following parameter: ‘client_secret or client_assertion”? Thanks in advance.

  37. Sree Chi 2019-09-05 at 14:17

    Hi Justin, Can you please let me know, how did you fix the following error: ”AADSTS70002: The request body must contain the following parameter: ‘client_secret or client_assertion”

    Thanks in advance.

  38. Justin Roby 2019-09-05 at 17:42

    It has been awhile since I fixed this and I made a ton of changes since then. I made a modification to the Microsoft github project for embedded powerbi. Here’s the URL to that project: https://github.com/microsoft/PowerBI-Developer-Samples. It took me awhile to understand all the processes involved in generating, obtaining, and passing the token. But ultimately I had to use the following approach.

    var authenticationResult = await authenticationContext.AcquireTokenAsync(serviceUri, clientID, credential)

  39. Binod 2019-11-07 at 21:33

    HI Justin I am using exactly same method as you mentioned , but i am still getting “AADSTS70002: The request body must contain the following parameter: ‘client_secret or client_assertion’.

    Could you please tell me how exactly you solved this issue.

    Thanks BG

  40. Justin 2019-11-08 at 06:09

    Do you have another form of communication you want to use? It may be a lot quicker to use something other than this form to get this resolved for you.

Leave a comment