Azure AD Outage Sept 28, 2020; Tracking ID SM79-F88

Microsoft Azure suffered an outage with Azure AD authentication (Americas) on Monday, September 28th 2020, due to a software change in the service, which Microsoft reported was rolled back. As with other service health issues in cloud services, Microsoft constantly ensure customers are kept up to date with service issues and remediation activities. Azure Status, is main place to visit when reviewing cloud service health across all regions. For any historical status issues, the status history is also available for view.

I’ve seen a number of articles on several websites clearly reporting the issues incorrectly and making wide assumptions on how Microsoft handles software code updates. But, it’s important to understand a few basic things before making wide assumptions…

Lets talk about outages…

This isn’t the first and last outage we will see in any cloud platform.

Azure AD has an SLA, it’s important to understand what the SLA is and why it is stated; don’t expect every cloud service to be up 100% all the time. This happened in traditional data centers, there are things that happen, the things that cause outages are investigated, they are remediated, lessons are learned and the affected service, or process which affected the service, has an opportunity to be improved as part of continual service improvement phases.

Failures occur in every industry

Unfortunately, there are failures and service outages across different industries that affect consumers on a daily basis. We can always remember things like:

The time power was lost in our houses

The time the car had a fault or a recall

The time when the internet connection dropped

How does this help?

Well, technically for the outage it doesn’t, but the one of the key takeaways from a service outage is the remediation plan. The postmortem which Microsoft provide as part of the response to the issue experienced is key to improving the service availability and governance around how the specific outage occurred can be rectified to stop similar issues occurring again.

How does this help you?

As I mentioned earlier, it can be frustrating when outages occur, but in the long run, you can expect that similar outages should not occur in the way that the previous outage manifested itself previously. You can be sure that every outage is taken seriously, remediated and you can only expect that there will be innovative phases and releases to improve upon the service in the near future, to ensure outages are kept to a minimum. I can clearly imagine that publicly, we only see a very small percentage of the communication that occurs with such incidents. To Microsoft, such incidents are a matter of paramount importance.

Whilst, customers can’t specifically design for an Azure AD outage, where many Microsoft services rely on Azure AD, one of the key points of cloud computing is to not expect everything to be up and running 100% all of the time. That said, every enterprise solution deployed to a public cloud platform should be designed for availability, with redundancy in mind utilising the correct architectural patterns for each service component.

Closing thoughts

This has all reminded me of a quote by Tom Peters;

“Almost all quality improvement comes via simplification of design, manufacturing… layout, processes, and procedures.”
Tom Peters

Microsoft officially announce support end dates for ADAL and the Azure AD Graph API

I still have some old projects I’ve worked on in my own personal code repository, which still utilise ADAL and the Azure AD Graph API. Without question, there most definitely software solutions on the market which still use the the same nuget packages or endpoints via REST API’s to authenticate users and pull back/update user profile information in a multitude of ways as required by the application.

Just incase you missed this, Microsoft have now provided official guidance on the end of support timelines for both the Active Directory Authentication Library and the Azure Active Directory Graph API.

I’ve been using the Microsoft Authentication Library and MS Graph for a few years now, as the guidance from Microsoft has always been to urge developers to use MSAL and MS Graph for all new development projects. So, now it’s official and time to move over and start migrating over and work with the Microsoft Identity Platform v2.0 endpoint and v2.0 tokens.

Blazor Apps: The Login Display – Part 2

This is part two of two, on Blazor Apps and the Login Display.

In my first post I discussed some basics around the LoginDisplay Razor component in Blazor server-side applications. In this post I will expand out the sample, to link the user display name value to navigate to another Razor component as a page to call MS Graph and return a basic set of user properties.

Updating your Blazor App Dependencies

You will need to update your application dependencies and introduce a new service, in this case a TokenProvider, to return the access token and refresh token, with additional scopes to call MS Graph. For simplicity of this article, this is well defined in the following article already by Microsoft.

ASP.NET Core Blazor Server additional security scenarios

MS Graph Client

Whilst you can build your own HTTP client to call the MS Graph REST APIs, Microsoft provide the Graph Client library for .Net, so in my sample I installed the nuget package in my project.

Azure AD Permissions

I already have the Azure AD permissions in place for the application to read user profiles in the tenant, but I’ve provided a sample below.

New UserProfile.cs Class

A new class is created for each profile property I was interested in displaying on the profile page. The code for the class is below. For simplicity, the types are all strings.

namespace BlazorProfile.Data
{
    public class UserProfile
    {
        public string ObjectId { get; set; }
        public string DisplayName { get; set; }
        public string Office { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string UPN  { get; set; }
        public string JobTitle { get; set; }
        public string Department { get; set; }
        public string MobilePhone { get; set; }
        public string CompanyName { get; set; }
        public string EmployeeId { get; set; }
    }
}

New LCProfile Component

I then created a new Razor page “LCProfile” to host the code required to utilise the TokenProvider service. The code for the component is below.

@page "/lcprofile"
@using BlazorProfile.Data
@inject AuthenticationStateProvider AuthenticationStateProvider
@using Microsoft.Identity.Client
@inject Microsoft.Extensions.Configuration.IConfiguration config
@using Microsoft.Graph
@using Microsoft.AspNetCore.Components.Authorization
@using System.Net.Http.Headers
@using Microsoft.AspNetCore.Http
@inject IHttpContextAccessor httpContextAccessor
@using BlazorProfile.Auth
@inject TokenProvider TokenProvider

<AuthorizeView>
    <Authorized>
        <h3>Profile</h3>
        <table class="table" width="500px">
            <tr>
                <td>First Name: @prof.FirstName</td>
            </tr>
            <tr>
                <td>Last Name: @prof.LastName</td>
            </tr>
            <tr>
                <td>ObjectId: @prof.ObjectId</td>
            </tr>
            <tr>
                <td>Display Name: @prof.DisplayName</td>
            </tr>
            <tr>
                <td>Company: @prof.CompanyName</td>
            </tr>
            <tr>
                <td>Job Title: @prof.JobTitle</td>
            </tr>
            <tr>
                <td>Department: @prof.Department</td>
            </tr>
            <tr>
                <td>Office: @prof.Office</td>
            </tr>
            <tr>
                <td>UPN: @prof.UPN</td>
            </tr>
            <tr>
                <td>Office Phone: @prof.MobilePhone</td>
            </tr>
        </table>
@ErrorMessage
</Authorized>
        <NotAuthorized>
            You have not authenticated!
        </NotAuthorized>
    </AuthorizeView>

@code {

        private string ErrorMessage;

        private UserProfile prof = new UserProfile();

        protected override async Task OnInitializedAsync()
        {
                ErrorMessage = "No exceptions on page load";
            try
            {

                var accesstoken = TokenProvider.AccessToken;

                var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
                var authenticateduser = authState.User.Claims.First(c => c.Type == "preferred_username");




                var graphClient = new GraphServiceClient(
                new DelegateAuthenticationProvider(requestMessage =>
                {
                    requestMessage.Headers.Authorization =
                        new AuthenticationHeaderValue("bearer", accesstoken);
                    return Task.FromResult(0);
                }));


                User graphuser = await graphClient.Users[authenticateduser.Value]
                   .Request()
                   .Select(u => new
                   {
                       u.GivenName,
                       u.Surname,
                       u.Id,
                       u.UserPrincipalName,
                       u.OfficeLocation,
                       u.Department,
                       u.JobTitle,
                       u.MobilePhone,
                       u.DisplayName,
                       u.EmployeeId,
                       u.CompanyName,
                       u.City
                   })
           .GetAsync();

                prof.FirstName = graphuser.GivenName;
                prof.LastName = graphuser.Surname;
                prof.ObjectId = graphuser.Id;
                prof.UPN = graphuser.UserPrincipalName;
                prof.Office = graphuser.OfficeLocation;
                prof.Department = graphuser.Department;
                prof.JobTitle = graphuser.JobTitle;
                prof.MobilePhone = graphuser.MobilePhone;
                prof.DisplayName = graphuser.DisplayName;
                prof.EmployeeId = graphuser.EmployeeId;
                prof.CompanyName = graphuser.CompanyName;

            }
            catch (Exception e)
            {
                //throw new Exception (e.Message);
                ErrorMessage = e.Message;
            }
        }
    }

The TokenProvider is injected into the component and then referenced in the GraphClient. I also use the AuthenticationStateProvider to pull back the users principal name, in order to query MS Graph for the users Azure AD profile data.

Since the default users profile data is limited, an explicit selection for the properties in the user profile are selected and returned as part of the request. The Razor component should contain an authorised view.

Note: This is sample code, so you should check for Nulls in your own implementation. In fact, you would want to ensure that you check each property and likely store the values i.e. in a container that can be then referenced by the application. Typically, you could implement the scope offline_access for the application to retrieve refresh tokens as this is explicitly required for the v2.0 token endpoint i.e. you don’t want your application to fail based on the fact it doesn’t have the necessary structure to utilise refresh tokens to request new access tokens, otherwise you will end up receiving the an error similar to the one below.

Kungfu Developer 
Profile 
First Name: 
Last Name: 
Objectld: 
Display Name: 
Company: 
Job Title: 
Department: 
Office: 
UPN: 
Office Phone: 
Log out 
About 
Code: InvalidAuthenticationToken Message: Access token has expired. Inner error: AdditionalData: date: request-id: 72849096-54fb-4199-9f17- 
d6c6a7e6bc25 ClientRequestld: 72849096-54fb-4199-9f17-d6c6a7e6bc25

Updating the LoginDisplay Razor Component

For this sample, I’ve simply updated the component to add <a href tag> around the @UserName property to access the new LCprofile Razor Component.

@inject AuthenticationStateProvider AuthNStateProvider

<AuthorizeView>
    <Authorized>
        <a href="/lcprofile" >@UserName</a>
        <a href="AzureAD/Account/SignOut">Log out</a>
    </Authorized>
    <NotAuthorized>
        <a href="AzureAD/Account/SignIn">Log in</a>
    </NotAuthorized>
</AuthorizeView>

The Result

When the user name is clicked in the header, the user navigates to the lcprofile razor component and the profile properties are displayed .

This reminds me of the days when I developed custom controls for SharePoint several years ago, for users to edit their own profiles.

This ends the sample on the update to the LoginDisplay component in Blazor server-side applications.

Blazor Apps: The Login Display

When you first start to develop Blazor sever-side web applications and for this topic, integrate identity into the application with Azure AD, one of the first things I immediately change in the template is the login display on the top header bar. You’ll notice that most .Net web applications always display the HttpContext User.Identity.Name, essentially a property of the IPrincipal object for the current signed in user. The implementation in Blazor server-side apps is found in the Shared\LoginDisplay.razor component in the project.

The LoginDisplay component is referenced in the MainLayout.razor component.

The easiest way to change the behavior and ideally display the users full name, is to display the “name” claim in the LoginDisplay.razor component from the users claims that are represented from the id_token, which effectively forms the basis for the users session in terms of the security context. This of course excludes any roles, unless you have injected any other role claims from Azure AD or as part of the OIDC flow. Alternatively, the whole user context can be derived within the application itself as required from a variety of sources e.g. roles or the applications internal storage mechanisms.

Updating the Login Display Component

One of the simplest ways to update the display name is to update the LoginDisplay.razor component directly. Information in the security context, within an authorised view, is readily accessible. I make a simple change to pull out the “name” claim from the identity by injecting the AuthenticationStateProvider in the code sample below, then reference the value in @UserName.

@inject AuthenticationStateProvider AuthNStateProvider

<AuthorizeView>
    <Authorized>
        @UserName
        <a href="AzureAD/Account/SignOut">Log out</a>
    </Authorized>
    <NotAuthorized>
        <a href="AzureAD/Account/SignIn">Log in</a>
    </NotAuthorized>
</AuthorizeView>

@code {

    public string UserName { get; set; }

    protected override async Task OnInitializedAsync()
    {
        try
        {
            var authState = await AuthNStateProvider.GetAuthenticationStateAsync();
            var user = authState.User;

            if (authState.User.Identity != null)
            {
                UserName = authState.User.Claims.First(c => c.Type == "name").Value;
            }
        }
        catch
        {
            UserName = "Exception:User display name not set!";
        }
    }
}

Then the signed in users display name is then added to the header.

In my next post I will show how this simple implementation can be expanded to include other pieces of information from the users Azure AD profile.

AADSTS53003 – BlockedByConditionalAccess

Recently I’ve been troubleshooting conditional access policy errors in relation to applications failing to allow users to login to specific applications. Conditional Access Policies (CAPs), are at the heart of identity security for Azure at present, to manage access to your applications with various conditions like where the user is logging in from, defining trusted sites and setting different access controls e.g. MFA.

One specific issue I have been troubleshooting is an error code from the Microsoft Identity Platform: AADSTS53003. A user was able to login from one specific site without any issues, but from another location the user received this application error, which was identified in the backend logs of the application.

After reviewing the Azure AD Sign-in logs in the tenant, there was nothing specific showing what this error related to, when sign-in logs were checked against the application itself. There wasn’t much available in terms of troubleshooting this error apart from backend logs, so it was time to understand the user flow to determine why one physical site the user simply could not sign in at all, but at other sites everything was fine. The trusted site was valid for the application in the CAPs, so this made the troubleshooting process a little more challenging.

Back to the drawing board

After understanding where the application was failing, I developed a ASP.Net Core Blazor web application to start troubleshooting this issue in a completely separate environment. Knowing the application was trying to call MS Graph on-behalf-of the user, which then failed, it was an easy setup to recreate in a Blazor server-side application. I used the MS Graph Client and pulled across some basic profile properties for a user in the test tenant, with an on-behalf-of flow to display the results in a basic table.

As this was a application for debugging purposes, I also added a page element, to include any errors on the page component to display any code errors. In the above screen shot, no exceptions were found. Now I had a basis to start to troubleshoot the issues.

I setup my conditional access polices in a similar manner to the production Azure AD tenant to block access from untrusted sites.

I had setup a single policy, with a single test user and tested the policy based on location. A simple setup where I had my development office location and a VM in Azure with a public IP address, which was trusted as this was an exclusion in this case.

I setup a second policy to block access to Office 365, as the production application also had API permissions to access Office 365 and also enforced MFA for the user.

Testing

Now for the test. The user accessed the application from a trusted location, but this resulted in a successful page load, with MFA as applied in the CAPs. At this point my application was only calling Microsoft Graph, just like the production application. I wasn’t concerned with Office 365 APIs at this time.

I asked the user to sign into login.microsoftonline.com, as we all know this is the Microsoft Identity Platform endpoint for all user, device and application access, which contains all the components we need to authenticate with Azure AD, and of course where we receive the AADSTS53003 error as part of the application access token request flow. At this point the user received the following error, after entering their password, even though MFA was enforced.

This was somewhat confusing as the user could sign in with MFA into the application, with MFA, but accessing login.microsoftonline.com, to sign into the users Azure AD tenant actually failed with the above error, without MFA.

The Cause

A CAP was which blocked Office 365 from untrusted locations did not have the users location trusted IP address assigned for exclusion of the policy. Well that was an easy fix, but the the application that had issues signing the user in from another location, other than their home location, failed to provide them access with the AADSTS53003 error, when it call MS Graph. What comes next?

Re-creating the behavior

I wanted to re-create the behavior in my test Blazor application, so I determined that the error was not to do with the direct relationship with MS Graph, and started to add some scopes for SharePoint Online.

I then launched the application in Visual Studio, and hey presto!! the error was re-produced.

Why did this occur?

I determined that the error was not to do with the direct relationship with MS Graph, but the fact when the application was calling MS Graph, on-behalf-of the user, requesting an access token for the MS Graph resource, it was the fact the application had scopes assigned to access Office 365 API’s in it’s manifest. So this error was then simple to re-create, the Office 365 block policy required the users site to be trusted as part of an exclusion, so the application would not be blocked from requesting an access token for the MS Graph resource.

Hopefully, this post will help someone troubleshoot a similar error.