question

techwatching avatar image
0 Votes"
techwatching asked azure-cxp-api edited

Azure AD / Teams dev- How to automate the App registration configuration for Teams Tabs SSO ?

I want to automate the configuration of Single Sign-On on the Teams Application Tab I developed. The Microsoft documentation described the manual process to configure in the Azure Portal the App Registration in Azure AD but there is not sample on how to automate that using azure cli, powershell or an ARM template.

What should I use to automate the creation / configuration of the App Registration necessary for Tab SSO ? Does anyone have a script sample that automates that ?

I have tried using Terraform / Pulumi to configure this but the Terraform Azure AD provider does not support yet setting up oauth permissions on an app registration.

azure-active-directoryazure-ad-app-registration
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

techwatching avatar image
1 Vote"
techwatching answered techwatching commented

Thansk to the samples on the GitHub repository mentionned by @soumi-MSFT and the answer of @SaurabhSharma-msft on the GitHub issue I created, I was able to solve my problem.


You can find below the complete PowerShell script I used based on all this elements.


Most of the code comes from the Github samples repo but I adapted it to the Teams SSO context and transformed it a little to use powershell commands using Microsoft Graph internally. You may have to comment / uncomment some lines depending on the context where you are using this script (locally or in an Azure pipeline). I am using it in an Azure PowerShell Task (in Azure Pipelines) so the tokens necessary to connect to Azure AD are directly retrieved from the executing context that uses a Service Connection.


[CmdletBinding()] param( [Parameter(Mandatory=$true, HelpMessage='Object identifier of application on which you want to configure the Teams Tab SSO')] [string] $applicationObjectId, [Parameter(Mandatory=$true, HelpMessage='Custom domain where you site hosting the teams tab is accessible.')] [string] $customDomainName )


<#.Description
This function configures the SSO on an existing app registration in Active Directory
#>
function ConfigureSSOOnApplication([string] $tenantId, [string] $applicationObjectId)
{
$app = Get-AzureADMSApplication -ObjectId $applicationObjectId

  # Do nothing if the app has already been configured
  if ($app.IdentifierUris.Count -gt 0) {
      Write-Host "Exiting, application already configured."
      return
  }
 
  # Expose an API
  $appId = $app.AppId
  Set-AzureADMSApplication -ObjectId $app.Id -IdentifierUris "api://$customDomainName/$appId"
  Write-Host "App URI set."
 
  # Create Service Principal from Application
  New-AzureADServicePrincipal -AppId $app.AppId -Tags {WindowsAzureActiveDirectoryIntegratedApp}
  Write-Host "Service Principal created."
 
  # Create access_as_user scope
  # Add all existing scopes first
  $scopes = New-Object System.Collections.Generic.List[Microsoft.Open.MsGraph.Model.PermissionScope]
  $app.Api.Oauth2PermissionScopes | foreach-object { $scopes.Add($_) }
  $scope = CreateScope -value "access_as_user"  `
      -userConsentDisplayName "Teams can access the user’s profile"  `
      -userConsentDescription "Allows Teams to call the app’s web APIs as the current user."  `
      -adminConsentDisplayName "Teams can access your user profile and make requests on your behalf"  `
      -adminConsentDescription "Enable Teams to call this app’s APIs with the same rights that you have"
  $scopes.Add($scope)
  $app.Api.Oauth2PermissionScopes = $scopes
  Set-AzureADMSApplication -ObjectId $app.Id -Api $app.Api
  Write-Host "Scope access_as_user added."
 
  # Authorize Teams mobile/desktop client and Teams web client to access API
  $preAuthorizedApplications = New-Object 'System.Collections.Generic.List[Microsoft.Open.MSGraph.Model.PreAuthorizedApplication]'
  $teamsRichClienPreauthorization = CreatePreAuthorizedApplication `
      -applicationIdToPreAuthorize '1fec8e78-bce4-4aaf-ab1b-5451cc387264' `
      -scopeId $scope.Id
  $teamsWebClienPreauthorization = CreatePreAuthorizedApplication `
      -applicationIdToPreAuthorize '5e3ce6c0-2b1f-4285-8d4b-75ee78787346' `
      -scopeId $scope.Id
  $preAuthorizedApplications.Add($teamsRichClienPreauthorization)
  $preAuthorizedApplications.Add($teamsWebClienPreauthorization)   
  $app = Get-AzureADMSApplication -ObjectId $applicationObjectId
  $app.Api.PreAuthorizedApplications = $preAuthorizedApplications
  Set-AzureADMSApplication -ObjectId $app.Id -Api $app.Api
  Write-Host "Teams mobile/desktop and web clients applications pre-authorized."
 
  # Add API permissions needed
  $requiredResourcesAccess = New-Object System.Collections.Generic.List[Microsoft.Open.MsGraph.Model.RequiredResourceAccess]
  $requiredPermissions = GetRequiredPermissions `
      -applicationDisplayName 'Microsoft Graph' `
      -requiredDelegatedPermissions "User.Read|email|offline_access|openid|profile"
  $requiredResourcesAccess.Add($requiredPermissions)   
  Set-AzureADMSApplication -ObjectId $app.Id -RequiredResourceAccess $requiredPermissions
  Write-Host "Microsoft Graph permissions added."

}


<#.Description
This function creates a new Azure AD scope (OAuth2Permission) with default and provided values
#>
function CreateScope(
[string] $value,
[string] $userConsentDisplayName,
[string] $userConsentDescription,
[string] $adminConsentDisplayName,
[string] $adminConsentDescription)
{
$scope = New-Object Microsoft.Open.MsGraph.Model.PermissionScope
$scope.Id = New-Guid
$scope.Value = $value
$scope.UserConsentDisplayName = $userConsentDisplayName
$scope.UserConsentDescription = $userConsentDescription
$scope.AdminConsentDisplayName = $adminConsentDisplayName
$scope.AdminConsentDescription = $adminConsentDescription
$scope.IsEnabled = $true
$scope.Type = "User"
return $scope
}


<#.Description
This function creates a new PreAuthorized application on a specified scope
#>
function CreatePreAuthorizedApplication(
[string] $applicationIdToPreAuthorize,
[string] $scopeId)
{
$preAuthorizedApplication = New-Object 'Microsoft.Open.MSGraph.Model.PreAuthorizedApplication'
$preAuthorizedApplication.AppId = $applicationIdToPreAuthorize
$preAuthorizedApplication.DelegatedPermissionIds = @($scopeId)
return $preAuthorizedApplication
}


Example: GetRequiredPermissions "Microsoft Graph" "Graph.Read|User.Read"

See also: http://stackoverflow.com/questions/42164581/how-to-configure-a-new-azure-ad-application-through-powershell

function GetRequiredPermissions(
[string] $applicationDisplayName,
[string] $requiredDelegatedPermissions,
[string]$requiredApplicationPermissions,
$servicePrincipal)
{
# If we are passed the service principal we use it directly, otherwise we find it from the display name (which might not be unique)
if ($servicePrincipal)
{
$sp = $servicePrincipal
}
else
{
$sp = Get-AzureADServicePrincipal -Filter "DisplayName eq '$applicationDisplayName'"
}


  $requiredAccess = New-Object Microsoft.Open.MsGraph.Model.RequiredResourceAccess
  $requiredAccess.ResourceAppId = $sp.AppId 
  $requiredAccess.ResourceAccess = New-Object System.Collections.Generic.List[Microsoft.Open.MsGraph.Model.ResourceAccess]
 
  # $sp.Oauth2Permissions | Select Id,AdminConsentDisplayName,Value: To see the list of all the Delegated permissions for the application:
  if ($requiredDelegatedPermissions)
  {
      AddResourcePermission $requiredAccess -exposedPermissions $sp.Oauth2Permissions -requiredAccesses $requiredDelegatedPermissions -permissionType "Scope"
  }
     
  # $sp.AppRoles | Select Id,AdminConsentDisplayName,Value: To see the list of all the Application permissions for the application
  if ($requiredApplicationPermissions)
  {
      AddResourcePermission $requiredAccess -exposedPermissions $sp.AppRoles -requiredAccesses $requiredApplicationPermissions -permissionType "Role"
  }
  return $requiredAccess

}


Adds the requiredAccesses (expressed as a pipe separated string) to the requiredAccess structure

The exposed permissions are in the $exposedPermissions collection, and the type of permission (Scope | Role) is


described in $permissionType

function AddResourcePermission(
$requiredAccess,
$exposedPermissions,
[string]$requiredAccesses,
[string]$permissionType)
{
foreach($permission in $requiredAccesses.Trim().Split("|"))
{
foreach($exposedPermission in $exposedPermissions)
{
if ($exposedPermission.Value -eq $permission)
{
$resourceAccess = New-Object Microsoft.Open.MsGraph.Model.ResourceAccess
$resourceAccess.Type = $permissionType # Scope = Delegated permissions | Role = Application permissions
$resourceAccess.Id = $exposedPermission.Id # Read directory data
$requiredAccess.ResourceAccess.Add($resourceAccess)
}
}
}
}


Pre-requisites

if ($null -eq (Get-Module -ListAvailable -Name "AzureAD")) {
Install-Module -Name "AzureAD" -Force
}


Import-Module AzureAD


$context = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile.DefaultContext
$graphToken = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, "https://graph.microsoft.com").AccessToken
$aadToken = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, "https://graph.windows.net").AccessToken
Connect-AzureAD -AadAccessToken $aadToken -MsAccessToken $graphToken -AccountId $context.Account.Id -TenantId $context.tenant.id


Comment the 4 above lines and uncomment the line below for local debugging

Connect-AzureAD -TenantId '----'

Connect-AzureAD
$token = [Microsoft.Open.Azure.AD.CommonLibrary.AzureSession]::AccessTokens['AccessToken']


ConfigureSSOOnApplication -applicationObjectId $applicationObjectId -tenantId $context.tenant.id


· 4
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

@techwatching, Thank you so much for sharing this out. Really appreciate it.

0 Votes 0 ·

However the formatting seems ugly, I don't know why and what I can do about it.

0 Votes 0 ·

@techwatching, yeah the formatting gets messed out a lot of time and we have provided a feedback on this to the backend team on this. The script looks pretty good and I will surely try that out today and see if it needs any retouch. But once again, this is really awesome, and you really lessened my work.

0 Votes 0 ·
Show more comments
soumi-MSFT avatar image
0 Votes"
soumi-MSFT answered techwatching commented

@techwatching, Automation capability for SAML application and configuring the SSO features is yet not available. There are ways to get the App registration created for a SAML application, but the configuration for the Basic SAML configuration cannot be pushed through any automation as of yet.

You can surely drop a feedback note here, which would help the product to


Hope this helps.

Do let us know if this helps and if there are any more queries around this, please do let us know so that we can help you further. Also, please do not forget to accept the response as Answer; if the above response helped in answering your query.

· 3
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

I don't really understand why you are talking about SAML application, I did not mention SAML application. As far as I know Teams Tab SSO use Oauth2 so what I was asking is how to automate the creation of oauth2 permissions on an Azure Ad App Registration in order to set up correctly an application registration for a Teams application using Tab SSO.

0 Votes 0 ·

@techwatching, I apologize for misinterpreting your statement. For OAuth 2.0 apps, we have an powershell script called Configure.ps1, which helps in configuring the app registration in AAD with mentioned permissions and app passwords. You can find the script here, inside the directory called AppCreationScripts.


You might have to modify the script a bit, based on your requirement. Do let us know if this works for you in automating the app registration creation.


0 Votes 0 ·

Thanks for the script, unfortunately it does not handle exposing an API / scope from this App registration which is the real issue of automating the Teams Tab SSO configuration. The script seems to be interesting when you have to set required permissions from other API like Graph APIs, not exposing an API that will be consummed by Teams mobile/desktop/web applications.

0 Votes 0 ·
JimmyYang-MSFT avatar image
0 Votes"
JimmyYang-MSFT answered techwatching commented

Hi techwatching!

Based on my research, you can grant your registered app permissions to Azure Storage manually:

https://docs.microsoft.com/en-us/azure/storage/common/storage-auth-aad-app

· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Thanks for your answer but I don't see how this is relevant to my question which does not concern Azure Storage but Teams Tab SSO which is done by exposing an API (creating an oAuth2 scope) in an App Registration. Moreover my question is about automating the configuration of this app registration (exposing an API on an App Registration) so I am looking for scripts (azure cli, powershell, arm template) or any other way to automate that not doing it manually (I already gave in my question the link to the Microsoft documentation permitting to do it manually).

0 Votes 0 ·
soumi-MSFT avatar image
0 Votes"
soumi-MSFT answered techwatching commented

@techwatching, You can expose an application as an API by setting the identifierUris. You can update the script by adding the following lines of code:

 $appName = "SampleAPI1"
 $app = New-AzureADApplication -DisplayName $appName -ReplyUrls "http://localhost:1234"
 Set-AzureADApplication -ObjectId $app.ObjectId -IdentifierUris ("api://" + $app.AppId) -Verbose

Once you run the Set-AzureADApplication cmdlet with the -IdentifierUris switch, you would find that the application would be exposed as an API to be consumed by other applications.

Hope this helps.

Do let us know if this helps and if there are any more queries around this, please do let us know so that we can help you further. Also, please do not forget to accept the response as Answer; if the above response helped in answering your query.


· 4
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

@techwatching, I wanted to followup and wanted to understand if the above response helped in answering your query. If it did, please do let us know so that we can help you further. Also, please do not forget to accept the response as Answer; if the above response helped in answering your query.

0 Votes 0 ·

Well I am still struggling on how to make it works. This script which is in the same repository that the one you mentionned helped me to understand what I have to do. So I succeeded in setting the identifier uri, adding the scope "access_as_user", but I don't know how to set the "preauthorized applications". I can't see any parameter in Set-AzureADApplication to do that. There is a StackOverflow post about that but I don't know if it's still relevant and I am not really convinced to call directly the graph API to do that.


2 Votes 2 ·

@techwatching, I do see that the Stackoverflow thread you shared already had the answer regarding the PS cmdlet to set the "preauthorized applications". I also checked the Github issue you had raised and had an internal connect on that too. I hope this is no more a blocker for you now. But feel free to reach out in case anything else comes up on this. I am also working on creating a complete script for app registration and the points you brought up is really great and hence consolidating all the features in a single script.
Your inputs would surely help us capture all the scenarios a user might want get automated.

1 Vote 1 ·
Show more comments