Onboarding users in ASP.NET Core using Microsoft Entra ID Temporary Access Pass and Microsoft Graph

The article looks at onboarding different Microsoft Entra ID users with a temporary access pass (TAP) and some type of passwordless authentication. An ASP.NET Core application is used to create the Microsoft Entra ID member users which can then use a TAP to setup the account. This is a great way to onboard users in your tenant.

Code: https://github.com/damienbod/MicrosoftEntraIdTapOnboarding

History

2023-12-31 Updated .NET 8, fixed TAP retry bug

The ASP.NET Core application needs to onboard different type of Microsoft Entra ID users. Some users cannot use a passwordless authentication (yet) and so a password setup is also required for these users. TAP only works with members and we also need to support guest users with some alternative onboarding flow. Different type of user flows are supported or possible:

  • ME ID member user flow with TAP and FIDO2 authentication
  • ME ID member user flow with password using email/password authentication
  • ME ID member user flow with password setup and a phone authentication
  • ME ID guest user flow with federated login
  • ME ID guest user flow with Microsoft account
  • ME ID guest user flow with email code

FIDO2 should be used for all enterprise employees with an office account in the enterprise. If this is not possible, then at least the IT administrators should be forced to use FIDO2 authentication and the companies should be planning on a strategy on how to move to a phishing resistant authentication. This could be forced with a PIM and a continuous access policy for administration jobs. Using FIDO2, the identities are protected with a phishing resistant authentication. This should be a requirement for any professional solution.

Microsoft Entra ID users with no computer can use an email code or a SMS authentication. This is a low security authentication and applications should not expose sensitive information to these user types.

Setup

The ASP.NET Core application uses Microsoft.Identity.Web and the Microsoft.Identity.Web.MicrosoftGraphBeta Nuget packages to implement the Microsoft Entra ID clients. The ASP.NET Core client is a server rendered application and uses an Azure App registration which requires a secret or a certificate to acquire access tokens.

The onboarding application uses Microsoft Graph applications permissions to create the users and initialize the temporary access pass (TAP) flow. The following application permissions are used:

  • User.EnableDisableAccount.All
  • User.ReadWrite.All
  • UserAuthenticationMethod.ReadWrite.All

The permissions are added to a separate Azure App registration and require a secret to use. In a second phase, I will look at implementing the Graph API access using Microsoft Graph delegated permissions. It is also possible to use a service managed identity to acquire a Graph access token with the required permissions.

Onboarding members using passwordless

When onboarding a new Microsoft Entra ID user with passwordless and TAP, this needs to be implemented in two steps. Firstly, a new Microsoft Graph user is created with the type member. This takes an unknown length of time to complete on Microsoft Entra ID. When this is finished, a new TAP authentication method is created. I used the Polly Nuget package to retry this until the TAP request succeeds. Once successful, the temporary access pass is displayed in the UI. If this was a new employee or something like this, you could print this out and let the user complete the process.

private async Task CreateMember(UserModel userData)
{
    var createdUser = await _meIdGraphSdkManagedIdentityAppClient
                    .CreateGraphMemberUserAsync(userData);

    if (createdUser!.Id != null)
    {
        if (userData.UsePasswordless)
        {
            var maxRetryAttempts = 20;
            var pauseBetweenFailures = TimeSpan.FromSeconds(3);

            var retryPolicy = Policy
                .Handle<ArgumentException>()
                .WaitAndRetryAsync(maxRetryAttempts, i => pauseBetweenFailures);

            await retryPolicy.ExecuteAsync(async () =>
            {
                try
                {
                    var tap = await _meIdGraphSdkManagedIdentityAppClient
                    .AddTapForUserAsync(createdUser.Id);

                    AccessInfo = new CreatedAccessModel
                    {
                        Email = createdUser.Email,
                        TemporaryAccessPass = tap!.TemporaryAccessPass
                    };
                }
                catch (Exception ex)
                {
                    // handle expected errors
                    if(ex.GetType() == typeof(HttpRequestException))
                        throw new ArgumentException(ex.Message);

                    if (ex.GetType() == typeof(Microsoft.Graph.Models.ODataErrors.ODataError))
                    { 
                        throw new ArgumentException(ex.Message);
                    }

                    // return 500 to UI
                    throw;
                }
                
            });
        }
        else
        {
            AccessInfo = new CreatedAccessModel
            {
                Email = createdUser.Email,
                Password = createdUser.Password
            };
        }
    }
}

The CreateGraphMemberUserAsync method creates a new Microsoft Graph user. To use a temporary access pass, a member user must be used. Guest users cannot be onboarded like this. Even though we do not use a password in this process, the Microsoft Graph user validation forces us to create one. We just create a random password and will not return this, This password will not be updated.

public async Task<CreatedUserModel> CreateGraphMemberUserAsync
	(UserModel userModel)
{
	if (!userModel.Email.ToLower().EndsWith(_aadIssuerDomain.ToLower()))
	{
		throw new ArgumentException("A guest user must be invited!");
	}

	var graphServiceClient = _graphService
		.GetGraphClientWithManagedIdentityOrDevClient();

	var password = GetRandomString();
	var user = new User
	{
		DisplayName = userModel.UserName,
		Surname = userModel.LastName,
		GivenName = userModel.FirstName,
		OtherMails = new List<string> { userModel.Email },
		UserType = "member",
		AccountEnabled = true,
		UserPrincipalName = userModel.Email,
		MailNickname = userModel.UserName,
		PasswordProfile = new PasswordProfile
		{
			Password = password,
			// We use TAP if a paswordless onboarding is used
			ForceChangePasswordNextSignIn = !userModel.UsePasswordless
		},
		PasswordPolicies = "DisablePasswordExpiration"
	};

	var createdUser = await graphServiceClient.Users
		.Request()
		.AddAsync(user);

	return new CreatedUserModel
	{
		Email = createdUser.UserPrincipalName,
		Id = createdUser.Id,
		Password = password
	};
}

The TemporaryAccessPassAuthenticationMethod object is created using Microsoft Graph. We create a use once TAP. The access code is returned and displayed in the UI.

public async Task<TemporaryAccessPassAuthenticationMethod?> 
	AddTapForUserAsync(string userId)
{
	var graphServiceClient = _graphService
		.GetGraphClientWithManagedIdentityOrDevClient();

	var tempAccessPassAuthMethod 
		= new TemporaryAccessPassAuthenticationMethod
	{
		//StartDateTime = DateTimeOffset.Now,
		LifetimeInMinutes = 60,
		IsUsableOnce = true, 
	};

	var result = await graphServiceClient.Users[userId]
		.Authentication
		.TemporaryAccessPassMethods
		.Request()
		.AddAsync(tempAccessPassAuthMethod);

	return result;
}

The https://aka.ms/mysecurityinfo link can be used to complete the flow. The new user can click this link and enter the email and the access code.

Now that the user is authenticated, he or she can add a passwordless authentication method. I use an external FIDO2 key.

Once setup, the user can register and authenticate. You should use at least two security keys.

This is an awesome way of onboarding users which allows users to authenticate in a phishing resistant way without requiring or using a password. FIDO2 is the recommended and best way of authenticating users and with the rollout of passkeys, this will become more user friendly as well.

Onboarding members using password 😦

Due to the fact that some companies still use legacy authentication or we would like to support users with no computer, we also need to onboard users with passwords. When using passwords, the user needs to update the password on first use. The user should add an MFA, if not forced by the tenant. Some employees might not have a computer and would like user a phone to authenticate. An SMS code would be a good way of achieving this. This is of course not very secure, so you should expect these accounts to get lost or breached and so sensitive data should be avoided for applications used by these accounts. The device code flow could be used together on a shared PC with the user mobile phone. Starting an authentication flow from a QR Code is unsecure as this is not safe against phishing but as SMS is used for these type of users, it’s already not very secure. Again sensitive data must be avoided for applications accepting these low security accounts. It’s all about balance, maybe someday soon, all users will have FIDO2 security keys or passkeys to use and we can avoid these sort of solutions.

Onboarding guest users (invitations)

Guest users cannot be onboarded by creating a Microsoft Graph user. You need to send an invitation to the guest user for your tenant. Microsoft Graph provides an API for this. There a different type of guest users, depending on the account type and the authentication method type. The invitation returns an invite redeem URL which can be used to setup the account. This URL is mailed to the email used in the invite and does not need to be displayed in the UI.

private async Task InviteGuest(UserModel userData)
{
	var invitedGuestUser = await _aadGraphSdkManagedIdentityAppClient
					.InviteGuestUser(userData, _inviteUrl);

	if (invitedGuestUser!.Id != null)
	{
		AccessInfo = new CreatedAccessModel
		{
			Email = invitedGuestUser.InvitedUserEmailAddress,
			InviteRedeemUrl = invitedGuestUser.InviteRedeemUrl
		};
	}
}

The InviteGuestUser method is used to create the invite object and this is sent as a HTTP post request to the Microsoft Graph API.

public async Task<Invitation?> InviteGuestUser
	(UserModel userModel, string redirectUrl)
{
	if (userModel.Email.ToLower().EndsWith(_aadIssuerDomain.ToLower()))
	{
		throw new ArgumentException("user must be from a different domain!");
	}

	var graphServiceClient = _graphService
		.GetGraphClientWithManagedIdentityOrDevClient();

	var invitation = new Invitation
	{
		InvitedUserEmailAddress = userModel.Email,
		SendInvitationMessage = true,
		InvitedUserDisplayName 
			= $"{userModel.FirstName} {userModel.LastName}",
		InviteRedirectUrl = redirectUrl,
		InvitedUserType = "guest"
	};

	var invite = await graphServiceClient.Invitations
		.Request()
		.AddAsync(invitation);

	return invite;
}

Notes

Onboarding users with Microsoft Graph can be complicated because you need to know which parameters and how the users need to be created. Microsoft Entra ID members can be created using the Microsoft Graph user APIs, guest users are created using the Microsoft Graph invitation APIs. Onboarding users with TAP and FIDO2 is a great way of doing implementing this workflow. As of today, this is still part of the beta release.

Links

https://entra.microsoft.com/

https://learn.microsoft.com/en-us/azure/active-directory/authentication/howto-authentication-temporary-access-pass

https://learn.microsoft.com/en-us/graph/authenticationmethods-get-started

https://learn.microsoft.com/en-us/azure/active-directory/authentication/howto-authentication-passwordless-security-key-on-premises

https://learn.microsoft.com/en-us/azure/active-directory/external-identities/external-identities-overview

https://learn.microsoft.com/en-us/azure/active-directory/external-identities/b2b-quickstart-add-guest-users-portal

3 comments

  1. […] Onboarding users in ASP.NET Core using Azure AD Temporary Access Pass and Microsoft Graph [#.NET #.NET Core #ASP.NET Core #Azure #Azure AD #AzureAD #Fido2 #graph #Microsoft.Identity.Web #MicrosoftGraph #tap] […]

  2. […] Onboarding users in ASP.NET Core using Azure AD Temporary Access Pass and Microsoft Graph (Damien Bowden) […]

  3. […] Onboarding users in ASP.NET Core using Azure AD Temporary Access Pass and Microsoft Graph […]

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.