When Microsoft launched Teams shared channels into public preview (according to MC390413, shared channels will GA in mid-July 2022) the rubber hit the road as tenant administrators tried to figure out the complexities of managing shared channels in production use. It’s true that Microsoft conducted a long private preview with many customers to get shared channels to the point where they squashed obvious bugs and delivered usable software. However, once software is exposed to the kind of examination that an application with 270 million monthly active users can create, other questions bubble to the surface.
Which brings me to the topic of controlling user access to shared channels. The cross-tenant access settings in the External identities section of the Azure AD admin center control which tenants your organization can access using Azure AD B2B Direct Connect. This is the underlying authentication mechanism for Teams shared channels. It allows users to authenticate in their home tenant and use that authentication, including MFA and device state claims, to access resources in other tenants, if permitted by other tenants.
Azure AD Sign-Ins Track Cross-Tenant Access
Microsoft’s guidance for cross-tenant access settings advises that you can use Azure AD sign in logs to figure out user access to other tenants. It’s true that you can use the PowerShell snippet provided there, but I think we can do better.
The code uses the Get-MgAuditLogSignIn cmdlet from the Microsoft Graph PowerShell SDK to look for sign in records where the resource tenant identifier (the organization delivering a resource like Teams) is not the same as the home tenant identifier (the organization holding the Azure AD sign in logs).
$TenantId = (Get-MgOrganization).Id
Get-MgAuditLogSignIn -Filter "ResourceTenantId ne '$TenantId'" -All:$True
The code works (the All switch doesn’t need $True), but the result of the query is a set of sign-in records for both Azure AD B2B Collaboration (guest accounts) and Azure B2B Direct Connect. This is a better filter if you want to focus on access to Teams shared channels:
Get-MgAuditLogSignIn -Filter "ResourceTenantId ne '$TenantId' and CrossTenantAccessType eq 'b2bDirectConnect'" -All
Next, although you might recognize the identifier for your tenant, it’s unlikely that you’ll know the identifiers for other tenants (like 22e90715-3da6-4a78-9ec6-b3282389492b). To translate these identifiers into human-friendly tenant names, we need another method.
We’re already connected to the Microsoft Graph, so we can use a Graph query to resolve the identifier into a tenant name.
Finding Tenant Names
Fortunately, a beta query called findTenantInformationByTenantId does the trick. There’s little documentation available, but by running it through the Invoke-MgGraphRequest cmdlet (runs any Graph query when an SDK cmdlet is unavailable), we can retrieve tenant data:
Name Value
---- -----
@odata.context https://graph.microsoft.com/beta/$metadata#microsoft.graph.tenantInformation
tenantId 22e90715-3da6-4a78-9ec6-b3282389492b
displayName o365maestros
federationBrandName
defaultDomainName o365maestros.onmicrosoft.com
I assume this web site, which can return the identifier of any Microsoft 365 tenant, uses a similar API.
Flow of the Script
The flow of the PowerShell script to analyze sign-in data is therefore:
Find sign-in records for Azure B2B Direct Connect activity. If you want to process records for Azure B2B Collaboration, change the filter to remove the check against the CrossTenantAccessType property.
Extract data from each record, including resolving external tenant identifiers to tenant names.
Report.
In normal circumstances, the sign-in data will feature just a few tenants. It would be slow to run a query to resolve the tenant identifier for every record. To ensure performance, the script resolves a tenant name the first time it is encountered and stores the tenant name identifier and name in a hash table. When the script processes subsequent records for the same tenant, it reads the information from the hash table.
The output of the script is a PowerShell list containing details of sign-ins which use cross-tenant access to connect to Teams shared channels in external tenants (Figure 1).
Figure 1: Viewing information about user connects to Teams shared channels
The data can be parsed to reveal statistics like which tenants use cross-tenant access:
Or to reveal the names of the users who connect to external tenants:
$Report | Group User | Sort Count -Descending | Format-Table Name, Count
Name Count
---- -----
Sean Landy 4
James Ryan 3
Ken Bowers 3
And so on. I’m sure you’ll find other ways to use the information to track what’s happening with Teams shared channels. The point is that the data is there if you need it. All that’s required is a little massaging of the information.
Insight like this doesn’t come easily. You’ve got to know the technology and understand how to look behind the scenes. Benefit from the knowledge and experience of the Office 365 for IT Pros team by subscribing to the best eBook covering Office 365 and the wider Microsoft 365 ecosystem.
7 Replies to “Track User Access to Teams Shared Channels with Azure AD Sign-In Logs”
Hi there Tony, i think this is the third time this year one of your articles has been extremely useful to me, pretty amazing to me that this particular article was posted by you on the very day i needed it! hope all well with you and yours (ps we were neghbours once!)
Hey Tony, as always extremely useful.
Finally, there is a way to translate the TenantID to a Domain name. I noticed two things I your script line 6 you are missing one ”
And what I noticed during my first steps with that API call I needed the Directory.AccessAsUser.All permission as well
Thanks. This is good example of the problems of finding the right permission to run Graph SDK commands. I had the right set in one tenant, but not in another… and I went with that set.
{"id":null,"mode":"button","open_style":"in_modal","currency_code":"EUR","currency_symbol":"\u20ac","currency_type":"decimal","blank_flag_url":"https:\/\/office365itpros.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/blank.gif","flag_sprite_url":"https:\/\/office365itpros.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/flags.png","default_amount":100,"top_media_type":"featured_image","featured_image_url":"https:\/\/office365itpros.com\/wp-content\/uploads\/2022\/11\/cover-141x200.jpg","featured_embed":"","header_media":null,"file_download_attachment_data":null,"recurring_options_enabled":true,"recurring_options":{"never":{"selected":true,"after_output":"One time only"},"weekly":{"selected":false,"after_output":"Every week"},"monthly":{"selected":false,"after_output":"Every month"},"yearly":{"selected":false,"after_output":"Every year"}},"strings":{"current_user_email":"","current_user_name":"","link_text":"Virtual Tip Jar","complete_payment_button_error_text":"Check info and try again","payment_verb":"Pay","payment_request_label":"Office 365 for IT Pros","form_has_an_error":"Please check and fix the errors above","general_server_error":"Something isn't working right at the moment. Please try again.","form_title":"Office 365 for IT Pros","form_subtitle":null,"currency_search_text":"Country or Currency here","other_payment_option":"Other payment option","manage_payments_button_text":"Manage your payments","thank_you_message":"Thank you for supporting the work of Office 365 for IT Pros!","payment_confirmation_title":"Office 365 for IT Pros","receipt_title":"Your Receipt","print_receipt":"Print Receipt","email_receipt":"Email Receipt","email_receipt_sending":"Sending receipt...","email_receipt_success":"Email receipt successfully sent","email_receipt_failed":"Email receipt failed to send. Please try again.","receipt_payee":"Paid to","receipt_statement_descriptor":"This will show up on your statement as","receipt_date":"Date","receipt_transaction_id":"Transaction ID","receipt_transaction_amount":"Amount","refund_payer":"Refund from","login":"Log in to manage your payments","manage_payments":"Manage Payments","transactions_title":"Your Transactions","transaction_title":"Transaction Receipt","transaction_period":"Plan Period","arrangements_title":"Your Plans","arrangement_title":"Manage Plan","arrangement_details":"Plan Details","arrangement_id_title":"Plan ID","arrangement_payment_method_title":"Payment Method","arrangement_amount_title":"Plan Amount","arrangement_renewal_title":"Next renewal date","arrangement_action_cancel":"Cancel Plan","arrangement_action_cant_cancel":"Cancelling is currently not available.","arrangement_action_cancel_double":"Are you sure you'd like to cancel?","arrangement_cancelling":"Cancelling Plan...","arrangement_cancelled":"Plan Cancelled","arrangement_failed_to_cancel":"Failed to cancel plan","back_to_plans":"\u2190 Back to Plans","update_payment_method_verb":"Update","sca_auth_description":"Your have a pending renewal payment which requires authorization.","sca_auth_verb":"Authorize renewal payment","sca_authing_verb":"Authorizing payment","sca_authed_verb":"Payment successfully authorized!","sca_auth_failed":"Unable to authorize! Please try again.","login_button_text":"Log in","login_form_has_an_error":"Please check and fix the errors above","uppercase_search":"Search","lowercase_search":"search","uppercase_page":"Page","lowercase_page":"page","uppercase_items":"Items","lowercase_items":"items","uppercase_per":"Per","lowercase_per":"per","uppercase_of":"Of","lowercase_of":"of","back":"Back to plans","zip_code_placeholder":"Zip\/Postal Code","download_file_button_text":"Download File","input_field_instructions":{"tip_amount":{"placeholder_text":"How much would you like to tip?","initial":{"instruction_type":"normal","instruction_message":"How much would you like to tip? Choose any currency."},"empty":{"instruction_type":"error","instruction_message":"How much would you like to tip? Choose any currency."},"invalid_curency":{"instruction_type":"error","instruction_message":"Please choose a valid currency."}},"recurring":{"placeholder_text":"Recurring","initial":{"instruction_type":"normal","instruction_message":"How often would you like to give this?"},"success":{"instruction_type":"success","instruction_message":"How often would you like to give this?"},"empty":{"instruction_type":"error","instruction_message":"How often would you like to give this?"}},"name":{"placeholder_text":"Name on Credit Card","initial":{"instruction_type":"normal","instruction_message":"Enter the name on your card."},"success":{"instruction_type":"success","instruction_message":"Enter the name on your card."},"empty":{"instruction_type":"error","instruction_message":"Please enter the name on your card."}},"privacy_policy":{"terms_title":"Terms and conditions","terms_body":null,"terms_show_text":"View Terms","terms_hide_text":"Hide Terms","initial":{"instruction_type":"normal","instruction_message":"I agree to the terms."},"unchecked":{"instruction_type":"error","instruction_message":"Please agree to the terms."},"checked":{"instruction_type":"success","instruction_message":"I agree to the terms."}},"email":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"Enter your email address"},"success":{"instruction_type":"success","instruction_message":"Enter your email address"},"blank":{"instruction_type":"error","instruction_message":"Enter your email address"},"not_an_email_address":{"instruction_type":"error","instruction_message":"Make sure you have entered a valid email address"}},"note_with_tip":{"placeholder_text":"Your note here...","initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"empty":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"not_empty_initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"saving":{"instruction_type":"normal","instruction_message":"Saving note..."},"success":{"instruction_type":"success","instruction_message":"Note successfully saved!"},"error":{"instruction_type":"error","instruction_message":"Unable to save note note at this time. Please try again."}},"email_for_login_code":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"Enter your email to log in."},"success":{"instruction_type":"success","instruction_message":"Enter your email to log in."},"blank":{"instruction_type":"error","instruction_message":"Enter your email to log in."},"empty":{"instruction_type":"error","instruction_message":"Enter your email to log in."}},"login_code":{"initial":{"instruction_type":"normal","instruction_message":"Check your email and enter the login code."},"success":{"instruction_type":"success","instruction_message":"Check your email and enter the login code."},"blank":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."},"empty":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."}},"stripe_all_in_one":{"initial":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"empty":{"instruction_type":"error","instruction_message":"Enter your credit card details here."},"success":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"invalid_number":{"instruction_type":"error","instruction_message":"The card number is not a valid credit card number."},"invalid_expiry_month":{"instruction_type":"error","instruction_message":"The card's expiration month is invalid."},"invalid_expiry_year":{"instruction_type":"error","instruction_message":"The card's expiration year is invalid."},"invalid_cvc":{"instruction_type":"error","instruction_message":"The card's security code is invalid."},"incorrect_number":{"instruction_type":"error","instruction_message":"The card number is incorrect."},"incomplete_number":{"instruction_type":"error","instruction_message":"The card number is incomplete."},"incomplete_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incomplete."},"incomplete_expiry":{"instruction_type":"error","instruction_message":"The card's expiration date is incomplete."},"incomplete_zip":{"instruction_type":"error","instruction_message":"The card's zip code is incomplete."},"expired_card":{"instruction_type":"error","instruction_message":"The card has expired."},"incorrect_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incorrect."},"incorrect_zip":{"instruction_type":"error","instruction_message":"The card's zip code failed validation."},"invalid_expiry_year_past":{"instruction_type":"error","instruction_message":"The card's expiration year is in the past"},"card_declined":{"instruction_type":"error","instruction_message":"The card was declined."},"missing":{"instruction_type":"error","instruction_message":"There is no card on a customer that is being charged."},"processing_error":{"instruction_type":"error","instruction_message":"An error occurred while processing the card."},"invalid_request_error":{"instruction_type":"error","instruction_message":"Unable to process this payment, please try again or use alternative method."},"invalid_sofort_country":{"instruction_type":"error","instruction_message":"The billing country is not accepted by SOFORT. Please try another country."}}}},"fetched_oembed_html":false}
Hi there Tony, i think this is the third time this year one of your articles has been extremely useful to me, pretty amazing to me that this particular article was posted by you on the very day i needed it! hope all well with you and yours (ps we were neghbours once!)
Hi Peter… Good to make touch by being helpful. Keep on reading!
Hey Tony, as always extremely useful.
Finally, there is a way to translate the TenantID to a Domain name. I noticed two things I your script line 6 you are missing one ”
And what I noticed during my first steps with that API call I needed the Directory.AccessAsUser.All permission as well
Keep up with this extremely good work
Cheers
Christoph
Thanks. This is good example of the problems of finding the right permission to run Graph SDK commands. I had the right set in one tenant, but not in another… and I went with that set.