Authenticating with Azure AD interactively and non-interactively
Protecting a site using Microsoft account
Using your Microsoft account to protect a website or a web api in Azure has become quite a breeze. By following these few steps, your site is protected in minutes:
- Create a free azure account
- Create and deploy your web/api site in Azure portal
- Navigate to your Site -> Authentication / Authorization in Azure portal and enable App Service Authentication, select Log in with Microsoft Account, save and off you go. Your newly site is authenticated and protected.
Potecting a site using Azure Ad
Now your site is protected and you are happy, but then new requirements starts to rise. E.g. you want to enable your site to other users with minimum amount of effort. You fool around a bit in Azure portal, StackOverflow and Google, and figure our that Azure Active Directory is a suitable way to go. You follow these steps and you have an Azure Ad.
To be able to use credentials to log in on the site, the app also needs to be registered in the Azure AD -> App registrations.
All new users that are supposed to have access to the website/app must be granted access. This can be done from the app it self, using one of the approaches listed below:
- Azure Active Directory -> App registrations -> select the app -> Settings -> Required permissions and finally click Grant permissions -> Enterprise applications
- Azure Active Directory -> select the app -> Users and groups -> Add users -> find the user and click select
My experience is that approach #1 does not always work as intended, so I just prefer to use the second approach.
The applications is now protected, and new users can be added on the go. By selecting the free tier for Azure AD, quite a few users(500K objects as MS calls them) can be added.
If the application is all off type ASP.Net MVC, you have at this moment achieved to protect your site and can live happily ever after. But if it is not, and it is more off the new modern kind, say a microservice-ish, and uses a web api to get som additional data, then the entire chain is not secured. E.g. there is an app and a web api. The app could be MVC or any may a more modern SPA app, created with Vue or React or whatever floats your boat and it interacts with the web api to get data. More or less de-facto way of building apps today.
The current setup with just protecting the app is illustrated below. The user interacts with app, and if it is the first time the user hits the url for the app, the Azure AD protection mechanism wakens up and the user is redirected to Azure AD log on page. The user provides credentials, and if all good, the user is then redirected back to the app uri. The app in turns uses the api to get data, which in turns uses some kind of storage.
Since the api is not protected the user, as smart as he or she is, can also get the data directly from the api. This also means that anybody else can fetch the data directly, and this is a scenario that we don't want to happen. To prevent the api from being exposed to the entire world, we start by protecting it using the same approach as we did for the app. The preferred scenario is as illustrated below:
Shortly described, the entire flow starts as previously by authenticating before using the app, and then in turn for the app to use the api, the app must authenticate against api to get the data. This can be achieved with two different approaches:
Using ClientCredential
As always the first thing I do when I want to use something I haven't used before, I google it; and for sure there was github repo example using clientCredentials. I cloned the repo, replaced settings in the App.Config, with my settings and it all worked as expected.
The key method to use to get the token from Azure Ad is the AcquireTokenAsync(string resource, ClientCredential clientCredential). So, I start off by providing the resource param. The resource could either be the api endpoint or the ApplicationID. I prefer to use the Application ID.
The second param is ClientCredential. By using the ClientCredential I have to provide the clientId, and appKey for the Web app accessing the Api. The clientId, also known as Appplication ID can be found be navigating to Azure Active Directory -> App registrations -> select your app -> Application ID. The AppKey must be generated for the first usage. When still on the Registered App window, navigate to Settings -> Keys -> add new key name in the description and click save. Remember to store the value, as it will remain hidden for ever. If you forget your key value, just generate new secret.
To sum it up this is the code snippet that retrieves the token:
private static async Task<string> GetTokenWithClientCredential()
{
var authority = string.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
var authContext = new AuthenticationContext(authority);
var clientCredential = new ClientCredential(clientId, appKey);
var result = await authContext.AcquireTokenAsync(todoListResourceId, clientCredential);
return result.AccessToken;
}
Using UserPasswordCredential
So, again, I do the magic with google, and woop, there is another github repo demostrating the usage of authenticating with Azure Ad credentials using the UserPasswordCredential. I clone the repo, run it and get the following:
Doing some research, I figure out that I'm running AzureAD V2, while this example is based on AzureAD V1. So I get curious whether it is possibile to obtain the token with user credentials also with AAD V2. Reading the exception message it clearly states that one of the following parameters are missing; client assertion or client secret and some more googling also gives indication that the client secret is missing. So how am I suppose to provide the client secret along with user credential? Well maybe just do a regular post. Before I do that I choose to inspect the request done with AcquireTokenAsync() using fiddler, and correctly client_secret is not provided:
Since I could not find a way of providing client secret to the AcquireTokenAsync() method I'll just manually created the url and do a regular PostAsync() using the HttpClient() class:
var azureAdEndpoint = new Uri(authority + "/oauth2/token");
var urlEncodedContent = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("scope", "openid"),
new KeyValuePair<string, string>("resource", todoListResourceId),
new KeyValuePair<string, string>("client_id", todoListResourceId), //using the api client id
new KeyValuePair<string, string>("username", _user),
new KeyValuePair<string, string>("password", _password),
new KeyValuePair<string, string>("client_secret", _clientSecret),
});
var result = await httpClient.PostAsync(azureAdEndpoint, urlEncodedContent);
{
var content = await result.Content.ReadAsStringAsync();
var authResult = JsonConvert.DeserializeObject<dynamic>(content);
return authResult.access_token;
}
As expected this provides me the correct token, that I can use from my web app to get data from my api.
The source code can be found here
Summary
In this post I have looked into how to protect a web app and api hosted in Azure, using Azure AD v2. The web app is protected using the regular AAD Authentication mechanism that interactively asks the user for the credential, while the web app silently or non-interactively authenticates against the api using the provided clientcredentials or userpasswordcredentials. Protecting a webapplication using this approach is not very dyanmic, and is maybe more suitable for existing web applications the needs to be migrated to Azure with little effort. If you on the other side need a more dynamic and flexible authentication, you should consider solutions like identityserver or implementing your own identity management using ASP.Net core identity
Note
At the moment writting this post, April. 4th, Microsoft has updated there git repo example with a newer version supporting Azure AD v2.0, and using Graph API
Resources used in this post:
https://github.com/Azure-Samples/active-directory-dotnet-native-headless
https://github.com/Azure-Samples/active-directory-dotnet-daemon
https://social.msdn.microsoft.com/Forums/sqlserver/en-US/d54c668f-d8e3-4662-b124-d9abc3176c8c/http-post-body-parameters-to-get-oauth2-token?forum=azurelogicapps
https://carldesouza.com/httpclient-getasync-postasync-sendasync-c/
https://github.com/AzureAD/azure-activedirectory-library-for-ruby/issues/57