Microsoft actively develops Azure AD external identities and doesn't do much with mail contacts. Maybe it's a good idea to migrate mail contacts to Azure AD guest accounts. This article explores what's involved in moving mail contacts over to Azure AD guest accounts using PowerShell.
Azure AD B2B Collaboration and Guest Accounts for SharePoint Sharing
Two recent message center notifications highlight closer integration between SharePoint Online and Azure AD. MC526130 (11 March) says that new tenants created after March 31, 2023 will automatically enable the SharePoint Online integration with Azure B2B integration. Existing tenants aren’t impacted by this change. The associated update, also scheduled for roll-out in late March, is MC525663 (10 March). The news here is that SharePoint Online site sharing will use the Azure B2B Invitation manager instead of the legacy SharePoint Invitation Manager (Microsoft 365 roadmap item 117557).
Rationalization Around Azure AD
The two updates rationalize existing sharing methods with external users and focus on Azure AD as the driving force for managing invitations. The journey toward Azure AD B2B Collaboration started in 2021, so it’s been a while coming. The project makes a lot of sense for both customers and Microsoft (their gain is through reduced engineering expenses).
Ten years ago, it was reasonable for SharePoint to manage site sharing invitations. Today, when the site collection-based architecture is replaced by single-sites and most sharing occurs through Microsoft 365 groups and Teams, it’s illogical for SharePoint Online to have its own mechanism. 280 million monthly active Teams users create a lot of work for SharePoint.
Another factor is that site sharing with external users is a relatively uncommon action today. Most external users join groups or teams and gain access to the group-connected site. Although non-group connected sites do exist, they’re in the minority and some of those sites (like hub and communication sites) aren’t candidates for sharing with external people. And of course, even site owners might be blocked from sharing sites by a sensitivity label.
Time to Review Applicable Policies
Overall, I don’t think the change will disrupt many organizations. As Microsoft notes “You may want to review your Azure B2B Invitation Manager policies.” Two policies are worthy of note. The first is the Azure B2B Collaboration policy, which includes an allow or deny list (but not both) of domains.
The policy is now found under Collaboration restrictions in the External Identities section of the Azure AD admin center (Figure 1). It is commonly used to block sharing with consumer domains (deny list) or to restrict collaboration to a set of known domains belonging to partner organizations (allow list). If the organization already supports guest accounts, it’s likely that the collaboration policy already exists. Even so, changes like this are useful reminders of the need for regular review of any policy that affects how external people access tenant resources.
Figure 1: Azure AD B2B Collaboration policy settings
Azure AD cross-tenant access policies are a more powerful and flexible mechanism to control external access through both Azure B2B collaboration and Azure AD direct connect (used for Teams shared channels). Cross-tenant access policies are still relatively new and don’t need to be implemented unless required for a specific reason, so your tenant might not use them yet.
Although the Azure AD B2B Collaboration policy is likely to dominate for the immediate future, over time, I expect a slow transition to take advantage of the granular control available in cross-tenant access policies. When an organization changes over, SharePoint Online will take advantage. Leveraging advances made in Azure AD is an excellent reason for SharePoint Online to embrace Azure AD more fully.
Review Guest Accounts Too
Azure AD B2B collaboration works but that doesn’t mean that you don’t need to manage guest accounts. As more sharing happens, more guest accounts end up in your Azure AD. Some guest accounts are used once to share a document. Others are in ongoing use as guest members of groups and teams access shared documents. It’s a good idea to keep an eye on guest accounts and remove them as they become obsolete.
Support the work of the Office 365 for IT Pros team by subscribing to the Office 365 for IT Pros eBook. Your support pays for the time we need to track, analyze, and document the changing world of Microsoft 365 and Office 365.
An Unreasonable Azure AD Sign-in Frequency Creates a Barrier to Productivity
I had an unpleasant surprise this week when the security team for one of the companies where I have a guest account decided to improve tenant security. I strongly support any effort to improve tenant security, especially when the effort means better use of multi-factor authentication. It’s a topic I’ll cover during the TEC Europe 2023 tour in London, Paris, and Frankfurt in April. Registration for those events is now open.
It’s always important to take a pragmatic and practical view of security and not to implement anything that has a significant impact on user productivity. All change can impact users, but most of the time people learn to live with change and it’s not disruptive. Unfortunately, deciding to increase the user sign-in frequency for Azure AD accounts can be extraordinarily disruptive if you go too far.
Azure AD sign-in frequency is the period before a user must sign in again when attempting to access a resource, like opening a SharePoint Online document, creating a message with OWA, or accessing a Teams channel. By default, Azure AD uses a rolling 90-day window for its sign-in frequency. In other words, once you successfully sign-into a tenant, Azure AD won’t ask you to sign-in again for another 90 days.
Revoking User Account Access
Ninety days sounds like a long time, and it is. But this period needs to be viewed through the prism of how Azure AD and Microsoft 365 applications work. For example, in early 2022, Microsoft enabled Continuous Access Evaluation (CAE) for all tenants. CAE is a mechanism that allows Azure AD to notify applications of a critical change in the directory, such as an updated password. Applications that understand CAE, like SharePoint Online, revoke existing access for the account to require the user to reauthenticate.
The Microsoft 365 admin center also includes an option to sign users out of all current sessions (Figure 1) to force them to reauthenticate.
Figure 1: Forcing a user to sign out and reauthenticate
Of course, you might want to do more than sign a user out. In some cases, like employee departures, you might want to block future sign-ins. This is an operation that’s easily scripted with PowerShell. For example, this code:
Retrieves the identifier for an Azure AD user account.
Disables the account.
Sets a new password.
Revokes all refresh tokens.
$UserId = (Get-MgUser -UserId Lotte.Vettler@Office365itpros.com).Id
# Disable the account
Update-MgUser-UserId $UserId -AccountEnabled:$False
# Set a new password
$NewPassword = @{}
$NewPassword["Password"]= "!DoneAndDusted?"
$NewPassword["ForceChangePasswordNextSignIn"] = $True
Update-MgUser -UserId $UserId -PasswordProfile $NewPassword -AccountEnabled:$True
# Revoke refresh tokens
$Status = Invoke-MgInvalidateUserRefreshToken -UserId $UserId
It might take a little time for the full block to be effective because tokens must expire, and clients recognize the need for reauthentication, but it will happen.
How Conditional Access Can Make Guest Accounts Miserable
The reason I had a problem was that the security team updated the conditional access policies for guest users to enforce a 60-minute sign-in frequency (Figure 2). This change had a horrible effect. Guests switching to the tenant with Teams inevitably resulted in an MFA challenge. Opening a document stored in SharePoint Online or OneDrive for Business in that tenant brought an MFA challenge. My day was filled with MFA challenges, except when sending email to people in the tenant to complain about the new policy. Email isn’t affected by conditional access policies.
Figure 2: Setting the sign-in frequency in an Azure AD conditional access policy
As Microsoft notes in their documentation, “Based on customer feedback, sign-in frequency will apply for MFA as well.” They understate the matter. Sign-in frequency does apply for MFA too.
I understand the motivation on the part of the security team. Forcing people to reauthenticate before they can access resources is a good thing. Using MFA is a good thing. Forcing MFA challenges every hour must be a brilliant change to make.
Only it isn’t. As an external person working with another company, the change made my productivity much worse, and I doubt that it added one iota to the overall security effectiveness of the tenant. The tenant did not use number matching and additional context for MFA challenges, so the constant MFA challenges were a great example of how user fatigue creeps in as I clicked and clicked again to say “yes, it’s me.” System-preferred authentication wasn’t used either, so while I used the Authenticator app, other guests might use relatively insecure SMS challenge/response.
Overall, the change made it unpleasant to work with the tenant and that’s bad. A one-hour sign-in frequency is just too rigid and strict. I don’t know of any other tenant (where I am a guest) that uses such a short frequency. Most tenants I know of use the 90-day default. Some use 7 days. The most security-conscious (before now) uses a 1-day frequency.
No Best Answer for All Tenants
In truth, I don’t know the best user sign-in frequency to use for either tenant or guest accounts. It all depends on the security posture that an organization wants to assume. But I can say that most tenants would be better off making sure that all accounts use MFA and eliminating the use of the less secure authentication methods before reducing the sign-in frequency. If you’re concerned about guest hygiene (in this case, how secure a guest account is), have a different and more restrictive conditional access policy for guest access while remembering the need to get work done through Azure B2B collaboration. And review guest accounts annually to remove unwanted and obsolete crud.
To me, bringing users along on the journey to better security is a better tactic than ramming heightened security down their throats. It’s always been that way.
So much change, all the time. It’s a challenge to stay abreast of all the updates Microsoft makes across Office 365. Subscribe to the Office 365 for IT Pros eBook to receive monthly insights into what happens, why it happens, and what new features and capabilities mean for your tenant.
Use PowerShell to Find and Remove Azure AD Guest Accounts Who Don’t Want to Join Your Party
A January 30 post by Microsoft’s Jef Kazimer about using Azure Automation with Managed Identities to remove unredeemed guests from Azure AD promised to be a good read. Jef is a Principal Program Manager in the Microsoft Entra organization. Apart from using Azure Automation (something that every tenant administrator should master), highlighting the Microsoft Graph PowerShell SDK V2.0 (currently in early preview) gave me another reason to read the article.
I have expressed some concerns about Microsoft’s plans for the V2.0 of the Microsoft Graph PowerShell SDK. Leaving those concerns aside, it’s always good to learn how others approach a problem, especially as I’ve recently covered similar ground in terms of how to decide to remove guest accounts from Azure AD using the SDK. The differences between the two methods of reviewing guest accounts is that Jef looks for instances where guest accounts never went through the invitation redemption process to fully validate their accounts. On the other hand, my script looks at how long it’s been since a guest signed into the tenant and the number of groups the account is a member of to determine “staleness.” Let’s consider how to review guest accounts based on unredeemed invitations.
Outlining the Process
On paper, the steps involved to find and remove guest accounts with unredeemed invitations are straightforward:
Find guest accounts that have not redeemed the invitations received to join the tenant.
Remove the accounts from Azure AD.
Jef’s article suggests that this should be a regular process executed by an Azure Automation job using a managed identity to sign into the Graph and run the necessary PowerShell commands. I agree and think this is a good way to make sure to clear out unwanted guest accounts periodically.
Where I disagree is the detail of how to find the guests and the need for V2.0 of the SDK. It’s possible to do everything outlined in the article with SDK V1.0 cmdlets.
The Need for Administrative Units
Jef uses a dynamic administrative unit (currently a preview feature) to manage guest accounts. While it’s certainly convenient to create a dynamic administrative unit and assign the user management role for the administrative unit to the managed identity, this approach is optional and creates a potential requirement for Azure AD Premium P1 licenses. If your organization has those licenses, using a dynamic administrative unit offers the advantage of reducing the scope for the managed identity to process Azure AD accounts.
In some organizations, using administrative units (both the standard and dynamic variants) could be overkill because user management is a task performed by one or two administrators. In larger organizations, granularity in user management can be a desirable aspect, which is why administrative units exist.
Finding Azure AD Guest Accounts with Unredeemed Invitations
The first step is to find the target set of guest accounts. The simplest way is to run the Get-MgUser cmdlet and filter accounts to look for guests:
The guest accounts we want are those that have the ExternalUserState property set to “PendingAcceptance.” In other words, Azure AD issued an invitation to the guest’s email address, but the guest never followed up to redeem their invitation. This amended call to Get-MgUser fetches the set of guest accounts with unredeemed invitations:
Jef’s version uses the Get-MsIDUnredeemedInviteUser cmdlet from the MSIdentityTools module to find guest accounts with unredeemed invitations. It’s certainly worth considering using the MSIdentityTools module to manage Azure AD, but it’s also worth understanding how to do a job with the basic tools, which is what I do here.
Determining the Age of an Unredeemed Invitation
It would be unwise to remove any Azure AD guest accounts without giving their owners a little time to respond. Taking vacation periods into account, 45 days seem sufficient time for anyone to make their minds up. The loop to remove unredeemed guest accounts needs to check how long it’s been since Azure AD issued the invitation and only process the accounts that exceed the age threshold.
Our script can check when Azure AD created an invitation by checking the ExternalUserStateChangeDateTime property, which holds a timestamp for the last time the state of the account changed. The only state change for the accounts we’re interested in occurred when Azure AD created the invitations to join the tenant, so we can use the property to measure how long it’s been since a guest received their invitation.
This code shows how to loop through the set of guests with unredeemed invitations, check if their invitation is more than 45 days old, and remove the account that satisfy the test. To keep a record of what it does, the script logs the deletions.
Figure 1: Old Azure AD guest accounts with unredeemed invitations
Caveats Before Removing Azure AD Guest Accounts
The code works and stale guest account disappear to the Azure AD recycle bin. However, the danger exists that some of the accounts might be in active use. Take guest accounts created to represent the email addresses of Teams channels. These email addresses represent a connector to import messages into Teams channels. No one can sign into these non-existent mailboxes so no one will ever redeem the guest invitations. However, the mail user objects created by Exchange Online for these guest accounts allow them to be included in distribution lists, added to address lists, and so on.
Another example is when a guest joins an Outlook group (a Microsoft 365 group whose membership communicates via email). Guest members of these groups do not need to redeem their invitation unless they intend to sign into the tenant to access Teams or SharePoint Online or another application that supports Azure B2B Collaboration. If you remove these guest accounts based on their invitation redemption status, some important email-based communication might fail, and that would be a bad thing.
One way around the issue is to mark Azure AD guest accounts used for these purposes by writing a value into an appropriate property. For instance, set the department to EMAIL. Here’s how to mark the set of guest accounts used to route email to Teams channels:
All of this goes to prove that setting out to automate what appears to be a straightforward administrative task might lead to unforeseen consequences if you don’t think through the different ways applications use the objects.
Using SDK V2.0
Coming back to using V2.0 of the Microsoft Graph PowerShell SDK, nothing done so far needs V2.0. The only mention of a V2.0-specific feature is the support for a managed identity when connecting to the Graph. The code used to connect is:
Connect-MgGraph -Identity
A one-liner is certainly convenient, but it’s possible to connect to a managed identity with the Graph SDK with code that is just a little more complicated. Here’s what I do:
Going from three lines to one is probably not a huge benefit!
So much change, all the time. It’s a challenge to stay abreast of all the updates Microsoft makes across Office 365. Subscribe to the Office 365 for IT Pros eBook to receive monthly insights into what happens, why it happens, and what new features and capabilities mean for your tenant.
Finding Azure AD Guest Accounts in Microsoft 365 Groups
The article explaining how to report old guest accounts and their membership of Microsoft 365 Groups (and teams) in a tenant is very popular and many people use its accompanying script. The idea is to find guest accounts above a certain age (365 days – configurable in the script) and report the groups these guests are members of. Any old guest accounts that aren’t in any groups are candidates for removal.
The script uses an old technique featuring the distinguished name of guest accounts to scan for group memberships using the Get-Recipient cmdlet. The approach works, but the variation of values that can exist in distinguished names due to the inclusion of characters like apostrophes and vertical lines means that some special processing is needed to make sure that lookups work. Achieving consistency in distinguished names might be one of the reasons for Microsoft’s plan to make Exchange Online mailbox identification more effective.
In any case, time moves on and code degrades. I wanted to investigate how to use the Microsoft Graph PowerShell SDK to replace Get-Recipient. The script already uses the SDK to find Azure AD guest accounts with the Get-MgUser cmdlet.
The Graph Foundation
Graph APIs provide the foundation for all SDK cmdlets. Graph APIs provide the foundation for all SDK cmdlets. The first thing to find is an appropriate API to find group membership. I started off with getMemberGroups. The PowerShell example for the API suggests that the Get-MgDirectoryObjectMemberGroup cmdlet is the one to use. For example:
The cmdlet works and returns a list of group identifiers that can be used to retrieve information about the groups that the user belongs to. For example:
Get-MgGroup -GroupId $Groups[0] | Format-Table DisplayName, Id, GroupTypes
DisplayName Id GroupTypes
----------- -- ----------
All Tenant Member User Accounts 05ecf033-b39a-422c-8d30-0605965e29da {DynamicMembership, Unified}
However, because Get-MgDirectoryObjectMemberGroup returns a simple list of group identifiers, the developer must do extra work to call Get-MgGroup for each group to retrieve group properties. Not only is this extra work, calling Get-MgGroup repeatedly becomes very inefficient as the number of guests and their membership in groups increase.
Looking Behind the Scenes with Graph X-Ray
The Azure AD admin center (and the Entra admin center) both list the groups that user accounts (tenant and guests) belong to. Performance is snappy and it seemed unlikely that the code used was making multiple calls to retrieve the properties for each group. Many of the sections in these admin centers use Graph API requests to fetch information, and the Graph X-Ray tool reveals those requests. Looking at the output, it’s interesting to see that the admin center uses the beta Graph endpoint with the groups memberOf API (Figure 1).
Figure 1: Using the Graph X-Ray tool to find the Graph API for group membership
We can reuse the call used by the Azure AD center to create the query (containing the object identifier for the user account) and run the query using the SDK Invoke-MgGraphRequest cmdlet. One change made to the command is to include a filter to select only Microsoft 365 groups. If you omit the filter, the Graph returns all the groups a user belongs to, including security groups and distribution lists. The group information is in an array that’s in the Value property returned by the Graph request. For convenience, we put the data into a separate array.
The format of returned data marks a big difference between the SDK cmdlet and the Graph API request. The cmdlet returns group information in a hash table in the AdditionalProperties array while the Graph API request returns a simple array called Value. To retrieve group properties from the hash table, we must enumerate through its values. For instance, to return the names of the Microsoft 365 groups in the hash table, we do something like this:
SDK cmdlets can be inconsistent in how they return data. It’s just one of the charms of working with cmdlets that are automatically generated from code. Hopefully, Microsoft will do a better job of ironing out inconsistencies when they release V2.0 of the SDK sometime later in 2023.
A Get-MgUserTransitiveMemberOf cmdlet is also available to return the membership of nested groups. We don’t need to do this because we’re only interested in Microsoft 365 groups, which don’t support nesting. The cmdlet works in much the same way:
Because of the extra complexity in accessing group properties, I decided to use a modified version of the Graph API request from the Azure AD admin center. It’s executed using the Invoke-MgGraphRequest cmdlet, so I think the decision is justified.
When revising the script, I made some other improvements, including adding a basic assessment of whether a guest account is stale or very stale. The assessment is intended to highlight if I should consider removing these accounts because they’re obviously not being used. Figure 2 shows the output of the report.
Figure 2: Report highlighting potentially obsolete Azure AD guest accounts
Reporting obsolete Azure AD guest accounts is nice. Cleaning up old junk from Azure AD is even better. The script generates a PowerShell list with details of all guests over a certain age and the groups they belong to. To generate a list of the very stale guest accounts, filter the list:
To complete the job and remove the obsolete guest accounts, a simple loop to call Remove-MgUser to process each account:
ForEach ($Account in $DeleteAccounts) {
Write-Host ("Removing guest account for {0} with UPN {1}" -f $Account.Name, $Account.UPN)
Remove-MgUser -UserId $Account.Id }
Obsolete or stale guest accounts are not harmful, but their presence slows down processing like PowerShell scripts. For that reason, it’s a good idea to clean out unwanted guests periodically.
Learn about mastering the Microsoft Graph PowerShell SDK and the Microsoft 365 PowerShell modules by subscribing to the Office 365 for IT Pros eBook. Use our experience to understand what’s important and how best to protect your tenant.
Moving to a New Mobile Phone Means New Codes for the Microsoft Authenticator App
Moving to a new mobile device always involves a certain amount of hassle. The advent of mobile authenticator apps makes the move a little harder, especially when guest accounts on other tenants are involved.
In my case, I moved from an oldish iPhone 11 to a new iPhone 14. I was very happy with the 11 and used it since 2019. However, its battery showed signs of age and I fancied a change, which is all the reason I needed to get the 14.
Moving apps from an old iPhone to a new device is very easy. Minor hassles like making Outlook the default mail app for iOS and adding Teams to the pinned app list are easily overcome. It’s all the messing around with app passwords and authentication that causes the hassle.
Which brings me to the Microsoft Authenticator app. I am a strong proponent of multi-factor authentication and use the authenticator app to protect my Microsoft 365 and other accounts, including services like GitHub and Twitter. The app has a backup and recovery capability that I used to restore details of the accounts I use with authenticator. Unhappily (as noted in the support article), “Only your personal and non-Microsoft account credentials are stored, which includes your username and the account verification code that’s required to prove your identity.”
MFA Responses by Microsoft Authenticator App Need Device-Specific Credentials
For Microsoft school or work (Azure AD) accounts, the article explains that accounts that use push notifications (like MFA challenges) need additional verification to recover information. Push notifications require using a credential tied to a specific device. To restore accounts protected by MFA using the authenticator app on the new phone, this means that “you must scan a QR code given to you by your account provider.
Figure 1: Listing sign-in methods for an Azure AD account
Note: If a user can’t access the My account page because they don’t have access to their old phone and therefore cannot respond to an MFA challenge, an administrator can temporarily downgrade the MFA requirement to SMS to allow the user to sign in and access the page.
Adding a QR Code for a New Device
Remember that the credential used by the Microsoft Authenticator app to respond to MFA challenges is device-specific. To generate a new QR code, click Add sign-in method and select Authenticator app from the list of options. You’ll then be told that you need to install the app, which is fine because it’s already on the device. Click Next to start the setup process and click Next again to see a new QR code for the app (Figure 2).
Figure 2: Generating a new QR code for the Microsoft Authenticator app
You can scan the code using Authenticator and once this happens, the connection between account, app, and credential works. The process includes a verification step to prove that the Authenticator app can use the credential.
After setting up Authenticator for a new device, you’ll have multiple Microsoft Authenticator entries in your sign-in methods list (one per device). It’s perfectly safe to remove the entries for devices that you no longer use.
Adding a QR Code for a Guest Account
Everything works very nicely for a full tenant account. Generating a QR code to allow Authenticator to satisfy MFA challenges for a guest account is a little more complicated. I have guest accounts in multiple Microsoft 365 organizations, mostly because I am a guest member of Teams in those organizations. Let’s assume that you see that a guest account shows up in Authenticator flagged with “Action required” (Figure 3). This means that Authenticator can’t satisfy challenges for this account because it doesn’t have the necessary credentials.
Figure 3: The Microsoft Authenticator app flags that action is needed to fix an account
To secure the credentials for the account, the trick is to use the option to switch organizations via the icon in the top right-hand corner of the My Account page. This reveals the set of organizations that your account belongs to, starting with your account in the home tenant and then listing the organizations (aka host tenants) where you have a guest account (Figure 4).
Figure 4: Selecting an organization where an account is a guest
Switching to another organization uses your account (the guest account in this case) to sign-into that organization. You can then use the Security Info page to go through the same steps to generate a new QR code and add it to the entry for the guest account in the Authenticator app. The Authenticator app should now be able to satisfy MFA challenges for the guest account when signing into the target organization.
Microsoft Authenticator App Restored to Good Health
Moving to a new iPhone isn’t something people do every day and it’s easy to forget how to renew credentials in different services. Getting new QR codes for the Authenticator app is in that category. Fortunately, the process isn’t quite as painful as I first anticipated after restoring the backup to my new phone and everything is now working as expected.
PS. If you use the Authenticator app on an Apple Watch, remember that from January 2023, the Authenticator app no longer supports WatchOS. Microsoft says that WatchOS is “incompatible with Authenticator security features.” I read that to mean that some of the changes Microsoft made recently to harden Authenticator against MFA fatigue like number matching and additional context just don’t work in the constrained real estate available for watch devices.