Väčšina správcov ADFS pravdepodobne pozná webovú aplikáciu Claims X-Ray od Microsoftu, ktorá sa môže hodiť pri ladení SAML tokenov:

Claims X-Ray UI Screenshot

Aplikáciu Claims X-Ray je možné zaregistrovať aj v v Azure Active Directory, i keď to nie je oficiálne podporované:

Claims X-Ray Application Registration Screenshot

Čím ďalej, tým viac je možné vnímať snahu Microsoftu presvedčiť svojich zákazníkov, aby premigrovali svoje aplikácie z ADFS do AAD. Preto si myslím, že Claims X-Ray ešte môže nadobudnúť na význame.

Registrácia tejto aplikácie v Azure AD portáli je relatívne priamočiara. Čo je ale výrazne náročnejšie, je spraviť všetky nastavenia výhradne pomocou Microsoft Graph PowerShell SDK. Keďže mi trvalo celý deň, než sa mi podarilo popasovať s nedostatočnou dokumentáciou a viacerými chybami v oficiálnom PowerShell module, rozhodol som o svoj výsledný skript podeliť. Možno tým niekomu ušetrím drahocenný čas. S drobnými zmenami je možné tento postup použiť k registrácii ľubovoľnej SAML aplikácie v Azure AD.

Podrobný popis nasledujúceho skriptu je dostupný v anglickej verzii tohoto článku.

#Requires -Version 5
#Requires -Modules Microsoft.Graph.Applications,Microsoft.Graph.Identity.SignIns

# Note: The required modules can be installed using the following command:
# Install-Module -Name Microsoft.Graph.Applications,Microsoft.Graph.Identity.SignIns -Scope AllUsers -Force

# Connect to AzureAD
# Note: The -TenantId parameter is also required when using a Microsoft Account.
Connect-MgGraph -Scopes @(
   'Application.ReadWrite.All',
   'AppRoleAssignment.ReadWrite.All',
   'DelegatedPermissionGrant.ReadWrite.All',
   'Policy.Read.All',
   'Policy.ReadWrite.ApplicationConfiguration'
)

# Register the application
[string] $appName = 'Claims X-Ray'
[string] $appDescription = 'Use the Claims X-ray service to debug and troubleshoot problems with claims issuance.'
[string] $redirectUrl = 'https://adfshelp.microsoft.com/ClaimsXray/TokenResponse'
[hashtable] $infoUrls = @{
    MarketingUrl =        'https://adfshelp.microsoft.com/Tools/ShowTools'
    PrivacyStatementUrl = 'https://privacy.microsoft.com/en-us/privacystatement'
    TermsOfServiceUrl   = 'https://learn.microsoft.com/en-us/legal/mdsa'
    SupportUrl          = 'https://adfshelp.microsoft.com/Feedback/ProvideFeedback'
}

[Microsoft.Graph.PowerShell.Models.IMicrosoftGraphApplication] $registeredApp =
   New-MgApplication -DisplayName $appName `
                     -Description $appDescription `
                     -Web @{ RedirectUris = $redirectUrl } `
                     -DefaultRedirectUri $redirectUrl `
                     -GroupMembershipClaims All `
                     -Info $infoUrls

Update-MgApplication -ApplicationId $registeredApp.Id `
                     -SignInAudience 'AzureADMyOrg' `
                     -IdentifierUris 'urn:microsoft:adfs:claimsxray'
# Configure application logo
[string] $logoUrl = 'https://www.dsinternals.com/assets/images/claims-xray-logo.png'
[string] $tempLogoPath = New-TemporaryFile
Invoke-WebRequest -Uri $logoUrl -OutFile $tempLogoPath -UseBasicParsing

Invoke-GraphRequest -Method PUT -Uri "https://graph.microsoft.com/v1.0/applications/$($registeredApp.Id)/logo" `
                    -InputFilePath $tempLogoPath `
                    -ContentType 'image/*'

Remove-Item -Path $tempLogoPath

# Create the service principal
[Microsoft.Graph.PowerShell.Models.IMicrosoftGraphServicePrincipal] $servicePrincipal =
   New-MgServicePrincipal -DisplayName $appName `
                          -AppId $registeredApp.AppId `
                          -AccountEnabled `
                          -ServicePrincipalType Application `
                          -PreferredSingleSignOnMode saml `
                          -ReplyUrls $redirectUrl `
                          -Notes $appDescription `
                          -Tags 'WindowsAzureActiveDirectoryIntegratedApp','WindowsAzureActiveDirectoryCustomSingleSignOnApplication'

# Generate a new token-signing certificate
[Microsoft.Graph.PowerShell.Models.IMicrosoftGraphSelfSignedCertificate] $tokenSigningCertificate =
   Add-MgServicePrincipalTokenSigningCertificate -ServicePrincipalId $servicePrincipal.Id `
                                                 -DisplayName "CN=$appName AAD Token Signing" `
                                                 -EndDateTime (Get-Date).AddYears(3)

# Delegate the User.Read permission
[Microsoft.Graph.PowerShell.Models.IMicrosoftGraphServicePrincipal] $microsoftGraph =
    Get-MgServicePrincipal -Filter "DisplayName eq 'Microsoft Graph'"

[Microsoft.Graph.PowerShell.Models.IMicrosoftGraphPermissionScope] $userReadScope =
    $microsoftGraph.Oauth2PermissionScopes | Where-Object Value -eq 'User.Read'

Update-MgApplication -ApplicationId $registeredApp.Id -RequiredResourceAccess @{
    ResourceAppId = $microsoftGraph.AppId
    ResourceAccess = @(@{
        id = $userReadScope.Id
        type = 'Scope'
    })
}

# Approve the User.Read permission on behalf of all tenant users
[Microsoft.Graph.PowerShell.Models.IMicrosoftGraphOAuth2PermissionGrant] $adminConsent =
    New-MgOauth2PermissionGrant -ClientId $servicePrincipal.Id `
                                -ConsentType AllPrincipals `
                                -ResourceId $microsoftGraph.Id `
                                -Scope $userReadScope.Value

# Assign the application to the current user
[Microsoft.Graph.PowerShell.Models.IMicrosoftGraphDirectoryObject] $currentUser =
    Invoke-GraphRequest -Method GET -Uri 'https://graph.microsoft.com/v1.0/me'

[string] $defaultAppAccessRole = [Guid]::Empty
[Microsoft.Graph.PowerShell.Models.IMicrosoftGraphAppRoleAssignment] $appAssignment =
   New-MgServicePrincipalAppRoleAssignedTo -ServicePrincipalId $servicePrincipal.Id `
                                           -ResourceId $servicePrincipal.Id `
                                           -AppRoleId $defaultAppAccessRole `
                                           -PrincipalType User `
                                           -PrincipalId $currentUser.Id

# Configure optional claims
[Microsoft.Graph.PowerShell.Models.IMicrosoftGraphOptionalClaims] $optionalClaims = [Microsoft.Graph.PowerShell.Models.MicrosoftGraphOptionalClaims]::DeserializeFromDictionary(@{
   Saml2Token = @(
      @{ Name = 'acct' },
      @{ Name = 'groups' }
   )
})

Update-MgApplication -ApplicationId $registeredApp.Id -OptionalClaims $optionalClaims

# Create a new claims mapping policy
[string] $allClaimsMapping = @'
{
   "ClaimsMappingPolicy": {
       "Version": 1,
       "IncludeBasicClaimSet": "true",
       "ClaimsSchema": [
           {
               "Source": "user",
               "ID": "userprincipalname",
               "SamlClaimType": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"
           },
           {
               "Source": "user",
               "ID": "userprincipalname",
               "SamlClaimType": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"
           },
           {
               "Source": "user",
               "ID": "userprincipalname",
               "SamlClaimType": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"
           },
           {
               "Source": "user",
               "ID": "mail",
               "SamlClaimType": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
           },
           {
               "Source": "user",
               "ID": "givenname",
               "SamlClaimType": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"
           },
           {
               "Source": "user",
               "ID": "surname",
               "SamlClaimType": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"
           },
           {
               "Source": "user",
               "ID": "streetaddress",
               "SamlClaimType": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/streetaddress"
           },
           {
               "Source": "user",
               "ID": "city",
               "SamlClaimType": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/locality"
           },
           {
               "Source": "user",
               "ID": "postalcode",
               "SamlClaimType": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/postalcode"
           },
           {
               "Source": "user",
               "ID": "state",
               "SamlClaimType": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/stateorprovince"
           },
           {
               "Source": "user",
               "ID": "country",
               "SamlClaimType": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/country"
           },
           {
               "Source": "user",
               "ID": "mobilephone",
               "SamlClaimType": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/mobilephone"
           },
           {
               "Source": "user",
               "ID": "telephonenumber",
               "SamlClaimType": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/homephone"
           },
           {
               "Source": "user",
               "ID": "facsimiletelephonenumber",
               "SamlClaimType": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/otherphone"
           },
           {
               "Source": "user",
               "ID": "employeeid",
               "SamlClaimType": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/employeeid"
           },
           {
               "Source": "user",
               "ID": "onpremisesimmutableid",
               "SamlClaimType": "http://schemas.microsoft.com/LiveID/Federation/2008/05/ImmutableID"
           },
           {
               "Source": "application",
               "ID": "objectid",
               "SamlClaimType": "http://schemas.microsoft.com/2012/01/requestcontext/claims/relyingpartytrustid"
           }
       ],
       "ClaimsTransformation": []
   }
}
'@

[Microsoft.Graph.PowerShell.Models.IMicrosoftGraphClaimsMappingPolicy] $allClaimsPolicy =
   New-MgPolicyClaimMappingPolicy -DisplayName 'Issue All Claims' -Definition $allClaimsMapping

# Assign the claims mapping policy to the application
New-MgServicePrincipalClaimMappingPolicyByRef -ServicePrincipalId $servicePrincipal.Id -OdataId "https://graph.microsoft.com/v1.0/policies/claimsMappingPolicies/$($allClaimsPolicy.Id)"

# Open the Claims X-Ray app in a browser
# Note that it might take a minute for the application to become accessible.
Start-Process ('https://myapps.microsoft.com/signin/{0}?tenantId={1}' -f $servicePrincipal.AppId,$servicePrincipal.AppOwnerOrganizationId)