Dynamic membership rule for Teams Room accounts

Teams Room accounts are usually excluded from conditional access. To do so, they have to be in a security group, which of course we don’t want to do manually.

Most companies choose to use a naming standard and simply use that as a rule to create an exclusion group. This is easy to circumvent, I can create a guest user with the right name et voila zero CA policies!

A better way is to identify the accounts based on their assigned licenses, e.g. Teams Rooms Basic (6af4b3d6-14bb-4a2a-960c-6c902aad34f3). This, however, is not supported as an Azure AD group membership rule as this is stored in the AssignedLicenses property which will throw an “Unsupported Property” error.

The assignedPlans property however, does not contain unique plans. E.g. Teams1 is also used in most other licenses. Same goes with mcomeetadv and whiteboard_plan3, this means we can’t use MS’s example for license-based rules, but we CAN compare all at once (a full / all match, instead of any).

The following rule only matches users that ONLY have TEAMS1, MCOMEETADV and WHITEBOARD_PLAN3:

(user.assignedPlans -all (assignedPlan.servicePlanId -in ["3e26ee1f-8a5f-4d52-aee2-b81ce45c8f40","57ff2da0-773e-42df-b2af-ffb7a2317929","4a51bca5-1eff-43f5-878c-177680f191af"] -and assignedPlan.capabilityStatus -eq "Enabled")) -and
not (user.assignedPlans -all (assignedPlan.servicePlanId -eq ""))

Here’s another example that would match users with either MS Teams Basic or MS Teams Pro:

((user.assignedPlans -all (assignedPlan.servicePlanId -in ["3e26ee1f-8a5f-4d52-aee2-b81ce45c8f40","57ff2da0-773e-42df-b2af-ffb7a2317929","4a51bca5-1eff-43f5-878c-177680f191af"] -and assignedPlan.capabilityStatus -eq "Enabled")) -or 
(user.assignedPlans -all (assignedPlan.servicePlanId -in ["41781fb2-bc02-4b7c-bd55-b576c07bb09d","3e26ee1f-8a5f-4d52-aee2-b81ce45c8f40","4828c8ec-dc2e-4779-b502-87ac9ce28ab7","c1ec4a95-1f05-45b3-a911-aa3fa01094f5","57ff2da0-773e-42df-b2af-ffb7a2317929","0feaeb32-d00e-4d66-bd5a-43b5b83db82c","4a51bca5-1eff-43f5-878c-177680f191af","92c6b761-01de-457a-9dd9-793a975238f7"] -and assignedPlan.capabilityStatus -eq "Enabled")))
-and 
not (user.assignedPlans -all (assignedPlan.servicePlanId -eq ""))

if you want to do something similar for other licenses, here are the options/combinations:

https://github.com/MicrosoftDocs/azure-docs/blob/main/articles/active-directory/enterprise-users/licensing-service-plan-reference.md

Powershell Cert based authentication against the Graph API using a certificate from Keyvault

In automation scenarios it is common to use a service principal (app based) to work with the Graph API, or in my example, with PNP PowerShell against SharePoint (but both scenario’s work the same).

First, you’d need a client certificate, e.g. like this:

$folder = "c:\users\JosLieben\Desktop"
$cert=New-SelfSignedCertificate -Subject "CN=JOS" -CertStoreLocation "Cert:\CurrentUser\My"  -KeyExportPolicy Exportable -KeySpec Signature -HashAlgorithm "SHA256" -Provider "Microsoft Enhanced RSA and AES Cryptographic Provider"
Export-Certificate -Cert $cert -FilePath "$folder\jos.cer" 
Export-PfxCertificate -Password (ConvertTo-SecureString $clientCertPwd -AsPlainText -Force) -Cert $cert -FilePath "$folder\jos.pfx"

You’d then upload the .cer file as a certificate on your service principal to let Azure AD recognize your cert as a valid ‘password’ for your app registration.

Next you’d upload your .pfx file into Keyvault.

Finally, you can use Powershell to construct an access token for a given scope:

$tenantId = "YOURTENANTID"
$clientId = "YOURCLIENTID"
$scope = "https://graph.microsoft.com/.default" #or, e.g. https://$($tenantName)-admin.sharepoint.com/.default openid profile offline_access
$secret = Get-AzKeyVaultSecret -VaultName "YOURKEYVAULTNAME" -Name cippCert -AsPlainText
$clientCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList @([Convert]::FromBase64String($secret),"",[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)

$header = @{
    alg = "RS256"
    typ = "JWT"
    x5t = [System.Convert]::ToBase64String(($clientCert.GetCertHash()))
} | ConvertTo-Json -Compress

$claimsPayload = @{
    aud = "https://login.microsoftonline.com/$tenantId/oauth2/token"
    exp = [math]::Round(((New-TimeSpan -Start ((Get-Date "1970-01-01T00:00:00Z" ).ToUniversalTime()) -End (Get-Date).ToUniversalTime().AddMinutes(2)).TotalSeconds),0)
    iss = $clientId
    jti = (New-Guid).Guid
    nbf = [math]::Round(((New-TimeSpan -Start ((Get-Date "1970-01-01T00:00:00Z" ).ToUniversalTime()) -End ((Get-Date).ToUniversalTime())).TotalSeconds),0)
    sub = $clientId
} | ConvertTo-Json -Compress

$headerjsonbase64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($header)).Split('=')[0].Replace('+', '-').Replace('/', '_')
$claimsPayloadjsonbase64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($claimsPayload)).Split('=')[0].Replace('+', '-').Replace('/', '_')

$preJwt = $headerjsonbase64 + "." + $claimsPayloadjsonbase64
$toSign = [System.Text.Encoding]::UTF8.GetBytes($preJwt)
$privateKey = $clientCert.PrivateKey
$alg = [Security.Cryptography.HashAlgorithmName]::SHA256
$padding = [Security.Cryptography.RSASignaturePadding]::Pkcs1
$signature = [Convert]::ToBase64String($privateKey.SignData($toSign,$alg,$padding)) -replace '\+','-' -replace '/','_' -replace '='

$jwt = $headerjsonbase64 + "." + $claimsPayloadjsonbase64 + "." + $signature

$Authbody = @{
    'tenant' = $tenantId
    'scope' = $scope
    'client_assertion_type' = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
    'client_id'     = $clientId
    'grant_type'    = 'client_credentials'
    'client_assertion' = $jwt
}        

$accessToken = (Invoke-RestMethod -Method post -Uri "https://login.microsoftonline.com/$($tenantid)/oauth2/v2.0/token" -Body $Authbody -ErrorAction Stop).accesstoken

The token can then be used to used to call Graph. And an example that shows how to use a sharepoint scoped token for the Sharepoint PNP PowerShell moddule:

#use scope: https://$($tenantName)-admin.sharepoint.com/.default

connect-PnPOnline -Url "https://$($tenantName)-admin.sharepoint.com" -AccessToken $accessToken -ReturnConnection