Identity management has evolved a long way from basic usernames and passwords, as have those who wish to compromise user accounts. Nowadays, there are so many considerations and complexities to authentication and authorization that creating your own solution tends not to be the best option.
In this chapter, we will explore the features and services that Microsoft offers to help developers handle user authentication and authorization without the need to create their own solutions. We’ll start by exploring the Microsoft identity platform and covering the core features for controlling access to your resources. We’ll then learn how to implement authentication using the Microsoft Authentication Library (MSAL), before moving on to Microsoft Graph and some of the ways it can help enhance your apps. We’ll finish with a look at shared access signatures (SAS) for authorizing access to storage resources.
By the end of this chapter, you will understand which features and services are available to you from Microsoft for handling user authentication and authorization within your apps.
In this chapter, we’ll cover the following main topics:
To follow along with the exercises in this chapter, you will need the following:
Code in Action videos for this chapter: https://bit.ly/3RWeZcZ
Nowadays, there are open standards such as OAuth 2.0 and OpenID Connect (OIDC) to help with handling user authentication and authorization, but even these have many complexities, especially when considering more than just a username and password – multi-factor authentication and conditional access, for example. The Microsoft identity platform provides several tools to help developers implement user authentication and authorization and does the heavy lifting for you.
We first made use of the Microsoft identity platform when we enabled authentication in Chapter 3, Creating Azure App Service Web Apps. At that point, we mentioned that we would discuss the details later, and here we are. As we saw in that chapter, the Microsoft identity platform helps to create applications that users can sign in to with their Microsoft work or school accounts, personal Microsoft accounts, as well as social or local accounts.
The Microsoft identity platform is built on top of the open standards previously mentioned: OIDC for authentication and OAuth 2.0 for authorization. OIDC is built on top of OAuth 2.0, so there are similarities between the two with regard to terminology and flow. Out of the box, the platform supports advanced security features such as multi-factor authentication, passwordless sign-in, single sign-on, and more, without the need for you to implement the functionality yourself. If your application is integrated with the Microsoft identity platform, you can natively take advantage of all the features on offer.
The platform contains multiple open source libraries (such as the MSAL) for various languages, and because it uses standards-compliant implementations of OAuth 2.0 and OIDC, it supports bringing in your own standards-compliant libraries as well. It also has a registration and configuration experience within the Azure portal, as well as the ability to programmatically configure your applications using the Microsoft Graph API and PowerShell.
Something we deferred discussing in depth until now when we enabled authentication on our web app was the Azure Active Directory (AAD) objects that are required for your applications to delegate identity and access management functions to AAD. Let’s discuss those now.
In order to delegate functions to AAD, your application needs to be registered with an AAD tenant, which creates an identity configuration for your application that allows integration with AAD. When we register an application, we control whether it’s intended to be a single tenant app (only accessible within the AAD tenant in which you’re creating the registration, also known as the home tenant) or a multi-tenant app (accessible from other AAD tenants as well).
You may remember from when we previously enabled authentication on the web app that we had an entry in App registrations and an entry in Enterprise applications within the Azure portal. We’ll quickly go over each of these now:
Each tenant that uses the application will have a service principal to represent that application, which can be used to define the access policy and permissions within that specific tenant. Only one app registration exists per application and that exists only within the home tenant of the application. So, the app registration is the global application object, and the service principal (enterprise application) is the per-tenant representation of that object, over which you have control of permissions and access policies, and so on. We’ll see these in more detail throughout this chapter.
Before we delve into a practical exercise to look at application objects and service principals, we should also talk about the different permission and consent types, as these will form part of our upcoming exercise.
As previously mentioned, the Microsoft identity platform uses OAuth 2.0, which is a method through which an application can access web-hosted resources on a user’s behalf. Web-hosted resources that integrate with the Microsoft identity platform have a resource identifier or application ID URI. For example, the Microsoft Graph resource has a URI of https://graph.microsoft.com. This is a first-party resource, but the same is also true for third-party resources integrated with the Microsoft identity platform.
Any web-hosted resources that integrate with the Microsoft identity platform can also define a set of permissions, allowing for third-party apps to request only the permissions they need to perform their functions. Users and admins can see what type of data the app can access, which we’ll discuss shortly.
In OAuth 2.0, these types of permission sets are known as scopes. You will also see them commonly called permissions. When an application needs a certain permission, it specifies the permission in the scope query parameter, which is indicated by appending the permission value to the web-hosted resource’s identifier or application ID URI. For example, an app that needs to sign you in with your AAD account and read your profile will need the https://graph.microsoft.com/User.Read permission (or scope). If you don’t specify the resource (for example, simply using User.Read), the Microsoft identity platform assumes the resource is Microsoft Graph. Again, although this is a first-party example, the same is true for other web-hosted resources, such as your own web APIs, for example.
There are two types of permissions supported by the Microsoft identity platform that you should be aware of.
These are the two permission types supported by the Microsoft identity platform:
Essentially, if the app is accessing something on behalf of the user, the user delegates permission to the app to act on their behalf. If the app acts on its own behalf without a signed-in user required, application permission is required to access resources. To grant permission to an application, consent is required. There are three types of consent.
The three consent types available with the Microsoft identity platform are the following:
One challenge is that this kind of consent only applies to delegated permissions, not application permissions. This means that if the permission(s) being requested require(s) admin consent and the user can’t provide that consent, the permission won’t be granted here.
One challenge with this is that when you’re specifying all permissions upfront, your app needs to know every resource that it would ever need access to in advance. A common use case for this is so that admins can provide consent on behalf of an entire organization.
For admins to be able to consent to permissions on behalf of an organization, these permissions need to be static permissions registered within the app registration portal.
When you expose your own APIs, you can define scopes and whether only admins can consent to the scope or both admins and users can consent.
To demonstrate the different consent types, let’s run through a very basic example:
We could have created an app registration beforehand and selected it here, but for the sake of this exercise, we’ll just create a new one and have the default settings applied.
This is an example of a delegated static permission. You can see that no admin consent is required, so each user will need to provide their consent for the first time unless an admin consents on behalf of the organization.
You should see the following permissions being requested:
Figure 7.1 – User.Read permissions requested
Because I’m an admin, I can check the checkbox to consent on behalf of the entire organization.
When you commit that new URL, you should see the calendar permission being requested in addition to the previous permissions (still don’t accept yet):
Figure 7.2 – Additional Calendars.Read permissions requested
We’ve just seen a very basic example of static consent with user.read and dynamic consent by adding calendars.read to the scope. We’ll use dynamic consent more later when we start coding. Let’s complete this section by showing admin consent. Now that we know our application needs to read the calendar of our users, as well as read their profile to be able to log them in, assuming we’re happy with that, let’s grant admin consent on behalf of the entire organization for both permissions:
As previously mentioned, the app registration is the global representation of the application, which is used as a template for the service principal in each tenant. Changes made to the app registration will propagate to each enterprise application/service principal. Let’s go into our enterprise application and grant the admin consent for these static permissions.
Having certain permissions requiring admin consent provides some security, but you may also want to only provide access to the application if certain conditions are met from devices, such as multi-factor authentication, if the devices are enrolled within Microsoft Endpoint Manager/Intune, or depending on other conditions such as network location. This can be achieved using conditional access.
To demonstrate conditional access with the web app we have just created, let’s create a conditional access policy that only allows access to the app if the device attempting to access it is of a certain operating system (just for demonstration purposes):
Remember, this is our tenant-specific representation of the application, so this is where we set up access and permissions for the application in our tenant.
We have just created a conditional access policy blocking access to our app for all but the excluded OS. Feel free to look through the other options available, where you can grant access when certain conditions are met and require things such as multi-factor authentication.
A link to further information can be found in the Further reading section of this chapter.
Figure 7.3 – Conditional access denying access to the application
We won’t use this app or the App Service plan again, so feel free to delete those resources if you don’t want to continue exploring and testing.
That was a very quick and simple example to both reinforce what we’ve been through regarding service principals and introduce you to conditional access policies. This is all we’re doing with conditional access in this chapter, so feel free to remove the policy from the app to avoid preventing your own access.
For the most part, as we’ve just seen, conditional access doesn’t require any code changes and there are no changes to the behavior of the app itself either. If the app requests a token indirectly or silently for a service, code changes will be required to handle challenges from conditional access.
Here are some scenarios that would require code changes for handling conditional access challenges:
We’ve just seen that conditional access can be applied to web apps, but it can also be applied to web APIs that your app might access. Our app didn’t require any code changes to handle conditional access challenges because it was not trying to access any resources or APIs. If our app was accessing an API that had a conditional access policy applied, it would need to comply with that conditional access policy. Implementing challenge handling would be required in this scenario.
With the understanding of the Microsoft identity platform that we now have, let’s look at how we can make use of the libraries available from MSAL, which make authentication and authorization easier within your application code.
MSAL allows you to get tokens from the Microsoft identity platform for authentication and accessing secure web APIs. For example, MSAL can be used for getting secure access to Microsoft Graph and other Microsoft APIs, as well as any other web APIs, including your own. If you’ve heard of Active Directory Authentication Library (ADAL), which integrates with AAD, MSAL is the successor of ADAL and integrates with the Microsoft identity platform, instead of being limited to AAD authentication only.
There are MSAL libraries available to support several languages and frameworks using a consistent API, including Android, Angluar.js, iOS, macOS, Go, Java, JavaScript and TypeScript frameworks, Node.js, Python, React, and – as you might expect – the .NET ecosystem. MSAL can be used to acquire tokens for web apps, web APIs, single-page apps, mobile and native applications, daemons, and server-side apps.
As we mentioned earlier in this chapter, modern authentication can be extremely complex to implement yourself. MSAL handles a lot of the heavy lifting for you. For example, here are some of the things MSAL does for you:
MSAL provides several different authentication flows, which can be used in various application scenarios.
Here are some of the flows provided by MSAL:
Figure 7.4 – Some of the authentication flows provided by MSAL
A link to further information on the flows and application scenarios can be found in the Further reading section of this chapter.
Before we start creating applications that use MSAL, there are some key pieces of information to cover first with regards to client applications.
The first thing to understand about client applications is that there are three categories, which have different libraries and objects available. These categories are as follows:
For your application to make use of MSAL, you need to initialize the app with MSAL. The recommendation when instantiating your app is to use the available application builders. For public client apps, we can use the PublicClientApplicationBuilder class, and for confidential client apps, we can use the ConfidentialClientApplicationBuilder class. Both provide the means to configure your app within code, a configuration file, or a combination.
It should be no surprise by now that if we want our application to integrate with the Microsoft identity platform so that we can sign users in, we need to register the application in our AAD tenant.
Let’s take what we’ve learned so far in this chapter and create a new app that integrates with the Microsoft identity platform and uses MSAL to acquire tokens. We’re going to create a top-level C# console app that uses MSAL.NET for this exercise, keeping things relatively simple:
Because our app will be a console app running on our device, that makes it a public app, hence selecting this option.
Notice that we’re not creating any certificates or secrets because our app will be using the user’s tokens and not those of the app/client itself. If we were creating a web app (which would be a confidential client app), we could use the credentials of the app registration instead of the user.
dotnet new console -n "<app name>"
dotnet add package Microsoft.Identity.Client
using Microsoft.Identity.Client;
const string _clientId = "<app/client ID>";
const string _tenantId = "<tenant ID>";
var app = PublicClientApplicationBuilder
.Create(_clientId)
.WithAuthority(AzureCloudInstance.AzurePublic, _tenantId)
.WithRedirectUri("http://localhost")
.Build();
Notice we’re passing in the client ID of our app registration via the _clientId variable, as well as setting the authority to the public Azure cloud, passing in our tenant ID via the _tenantId variable. We’re also setting the app to use the same redirect URI as the app registration. If the redirect URIs don’t match, you will receive an error when trying to sign in.
string[] scopes = { "User.Read" };
AuthenticationResult result = await app.AcquireTokenInteractive(scopes).ExecuteAsync();
Console.WriteLine($"ID: {result.IdToken}");
Console.WriteLine($"Access: {result.AccessToken}");
Your code should now look like this:
using Microsoft.Identity.Client;
const string _clientId = "<app/client ID>";
const string _tenantId = "<tenant ID>";
var app = PublicClientApplicationBuilder
.Create(_clientId)
.WithAuthority(AzureCloudInstance.AzurePublic, _tenantId)
.WithRedirectUri("http://localhost")
.Build();
string[] scopes = { "User.Read" };
AuthenticationResult result = await app.AcquireTokenInteractive(scopes).ExecuteAsync();
Console.WriteLine($"ID: {result.IdToken}");
Console.WriteLine($"Access: {result.AccessToken}");
dotnet build
dotnet run
You should get the usual Permissions requested prompt, which you can accept. You should then have both tokens output to the console. If you wish, you can copy each token into https://jwt.ms as before and see what claims are contained within each token.
string[] scopes = { "User.Read", "Calendars.Read" };
dotnet run
You’ve just created an app that uses MSAL to request tokens and dynamic consent to request additional permissions through code. Congratulations!
Having to log in interactively every time is not exactly optimal, so you’d want to be able to acquire a token silently from the cache first and, only if that fails, try interactively. If this was a web app, the token cache would be handled for you. If you want to check out the mvc template arguments or the webapp template arguments, including --auth, you can find them here: https://docs.microsoft.com/dotnet/core/tools/dotnet-new-sdk-templates#web-options. As you will see from that page, you can pass in the app registration credentials, which get prepopulated in the app configuration.
With desktop apps such as the one we’ve just created, there’s no built-in user token cache handling, because storing unencrypted (although encoded) tokens locally in a file would not be considered secure.
To see our example expanded to include creating a local user cache (for demonstration purposes only), as well as attempting to obtain the tokens from the cache silently, falling back to interactive if the app can’t acquire a valid token from the cache, check out the example here: https://github.com/PacktPublishing/Developing-Solutions-for-Microsoft-Azure-AZ-204-Exam-Guide/tree/main/Chapter07/02-auth-with-cache.
The main point to note from the example code at the link is within the ObtainTokenAsync() method, where we first try to get the token from the cache with the AcquireTokenSilent() method, then if we get the MsalUiRequiredException exception, we try interactively as we did before. This is a common pattern with MSAL. We won’t be using this example for any upcoming exercises, as it was just intended to demonstrate a common pattern for token acquisition without having to create a new web app.
With that, we’ve reached the end of the MSAL section of this chapter. You can see how the APIs exposed by MSAL make authentication so much easier than if we had to manage all the protocol-level details ourselves.
Now that we’ve got our app integrated with the Microsoft identity platform using MSAL, we can consider expanding our app to make use of the tokens we acquired and start interacting with data in Microsoft 365 using Microsoft Graph.
At a very high level, Microsoft Graph is a REST API that can be used to interact with the data within Microsoft 365, available through REST APIs and client libraries.
You can use Microsoft Graph to access data on Microsoft 365 core services such as Calendar, Excel, Microsoft Search, OneDrive, Outlook/Exchange, Teams, and more. You also have Enterprise Mobility + Security services such as AAD, Advanced Threat Protection, and so on, Windows services, and Dynamics 365 Business Central services.
Microsoft Graph has three main components to help with the access and flow of data:
The Graph API can provide admin consent for the entire organization and specific resource types, whereas Data Connect can provide admin consent for select groups of users, resource types, and resource properties, and exclude users. Data Connect can also scope to many users or groups, whereas the Graph API can scope to a single user or the entire tenant.
Before we start querying, I’d like to introduce you to a useful tool for exploring Graph, so you can see the information you can get, which permissions are required, and what responses might look like. This tool is aptly named Graph Explorer.
We’ll briefly look at Graph Explorer now before we make more use of it later, like so:
Notice toward the top of the main part of the screen, where we have the method (defaults to GET), the API version (v1.0 at the time of writing this), and the URL. The URL is made up of the API endpoint (https://graph.microsoft.com), the API version (which updates if you change the version dropdown), the resource you want to query (defaults to /me, so will query the context of the logged-in user), and it will also include any optional parameters.
Some of the queries you might test won’t work until you modify the permissions by granting consent to Graph, which can be done here.
Feel free to explore a little more if you’d like. If we were to do the REST requests ourselves, we would need to acquire an access token, which we would add to an Authorization header with the value of Bearer <access token>. You’ll see this header being referenced in the upcoming C# example as well. You can use the browser developer tools to see what happens behind the scenes when you run a query, and you’ll see the content of the REST call that Graph performs.
A link to the reference for the Microsoft Graph API can be found in the Further reading section of this chapter. With a basic understanding of what makes up a Graph REST API query (method, version, resource, and optional parameters), let’s look at querying using the SDKs.
The Graph SDKs consist of two components: a core library and a service library. The core library provides many features for working with Microsoft Graph, with support for retry handling, transparent authentication, payload compression, and more. The service library has models and request builders generated from Microsoft Graph metadata.
Let’s expand the app we created earlier with interactive token acquisition to query Graph. You’re welcome to use the example that uses the local user cache, but as it’s not a good practice to have a local unencrypted cache, the upcoming exercise will be using the app without a local user token cache:
git clone https://github.com/PacktPublishing/Developing-Solutions-for-Microsoft-Azure-AZ-204-Exam-Guide
dotnet build
dotnet run
You should be prompted for authentication in your browser and once authenticated, you should be greeted with your given name. This queried Microsoft Graph to get your profile and then returned your given name from that profile data.
There are a few points to note in this code:
var user = await graphClient.Me.Request().GetAsync();
If you’d like to see an example of this app being expanded to create a new calendar event, feel free to check out https://github.com/PacktPublishing/Developing-Solutions-for-Microsoft-Azure-AZ-204-Exam-Guide/tree/main/Chapter07/04-graph-calendar-event. As with the local cache example, this is purely for demonstration purposes and I didn’t add any additional exception handling to it, as it’s just intended to show that it is relatively easy to implement features such as these.
Now that we’ve covered how to implement authentication and authorization with MSAL and how to use the acquired tokens to query the Microsoft Graph, we should consider the situations in which you need to provide your app with secure access to resources within a storage account, without trusting your app with the storage account access key. This is where SAS come in.
A SAS is a signed URI that provides defined access rights to specific resources within a storage account for a specified period. To use a SAS for accessing Azure Storage resources, you’ll need to have two components:
The SAS token itself is comprised of several elements and it’s worth understanding the structure.
If we look at an example SAS token, we can inspect each element for our understanding: sp=rd&st=2022-06-04T13:35:54Z&se=2022-06-04T21:35:54Z&spr=https&sv=2020-08-04&sr=b&sig=wX4run5CPuFbQkCezJxGwOEv%2BQ2ODjVEVxn5Yrzo8ug%3D. Let’s take each element and explore its meaning:
Using our example, the full SAS URI would be https://myaccount.blob.core.windows.net/container/file.txt?sp=rd&st=2022-06-04T13:35:54Z&se=2022-06-04T21:35:54Z&spr=https&sv=2020-08-04&sr=b&sig=wX4run5CPuFbQkCezJxGwOEv%2BQ2ODjVEVxn5Yrzo8ug%3D. Providing a request is made using the specified protocol and within the validity period, the SAS could be used to read and delete (in our example) file.txt within the container called container (I’m treating you to my incredibly expansive imagination again) within the myaccount storage account. Using this SAS wouldn’t require any AAD user credentials, as the SAS contains everything needed to provide access.
You might be wondering which key is used to sign the SAS token. This depends on the type of SAS you generate.
There are three supported types of SAS available in Azure Storage:
Let’s briefly demonstrate a SAS by creating the resources using the CLI, before jumping into the portal to look at the options available. You can generate a SAS using the CLI, but we’ll use the portal because it’s easier to demonstrate:
az group create -n "<name>" -l "<location>"
az storage account create -n "<name>" -g "<resource group name>" -l "<location>" --sku "Standard_LRS"
az storage account keys list -n "<name>" -g "<resource group name>"
These are the keys mentioned previously that are used to sign service and account SAS. As you can see from the output, they provide full permissions to your storage account, so you should keep these keys secure and rotate them regularly. The reason for having two keys is so that you can use one of the keys while you rotate the other to avoid access being lost.
az storage container create -n "<name>" --account-name "<storage account name>" --account-key "<account key>"
Notice that the URLs for the different services are populated, including the SAS token that is appended to the URLs. There was no option to use AAD credentials to secure the SAS because that’s only available for the Blob service.
This time, we have a Signing method option available, one of which is the account key, which we’ve already used. The other option allows us to generate a user delegation key.
If you see a warning displayed about not having permissions, you could go into the Access Control (IAM) blade and assign yourself a role that does have permissions – Storage Blob Data Contributor, for example. This can take some time to apply. You may have to refresh the page after the assignment.
It may have crossed your mind that Azure doesn’t appear to track these generated SAS tokens, so how would you edit or revoke one once generated? That’s where stored access policies come in.
Stored access policies provide another level of control over service-level SAS on the server side. Within a stored access policy, you specify permissions along with start and end times, which will be applied to any SAS that are assigned to the policy.
What this means is that you can create a SAS that uses a certain stored access policy, and it will inherit the permissions and dates from this policy. Another benefit of using stored access policies is that you can edit permissions and/or dates within the policy, and all SAS assigned to it will have those changes applied to them, so you can revoke all SAS assigned to a policy at once by changing the dates or deleting the policy.
Let’s do a quick demonstration with the storage account and container we’ve just created:
curl "<SAS URL>"
The request should have been successful and displayed the content of your plain text file. You may need to add the -UseBasicParsing switch if this command doesn’t work without it.
Figure 7.5 – A successful GET request using the Blob SAS URL to display content
As you might imagine, associating SAS tokens with stored access policies provides flexibility and additional security, so is a recommended practice where possible.
Although we won’t cover how to do it, generating SAS can also be done programmatically. This is common in situations where you have an app that requires users to read and write data to your storage account. In these scenarios, you might want to have a lightweight service authenticating the client and generating the SAS. Then, the client can use the SAS per the permissions and period defined by the SAS. A link to documentation on SAS can be found in the Further reading section of this chapter.
In this chapter, we have explored some of the key tools and features available to make building applications with authentication and authorization easier. We started with a detailed introduction to the Microsoft identity platform, which included explanations of app registration and service principals, followed by the different permission types and consent types, finishing with a demonstration of using conditional access to limit access to an application.
Building on this, we have looked at using the Microsoft Authentication Library to handle authentication and handle tokens in code. Once we had the tokens, we used them to query Microsoft Graph using the Graph SDK, after exploring Graph Explorer and the structure of Graph REST API requests. We finished off this chapter looking at how SAS can provide defined access to specific resources within a storage account, including using stored access policies for greater security and flexibility.
In the next chapter, we will stick with the theme of security and look at securing and accessing application secrets in Azure Key Vault. We’ll look at implementing managed identities for resources in Azure and we’ll finish by discussing how to store configuration in App Configuration.
18.224.6.211