Skip to content

Commit 1eb802d

Browse files
authored
Merge pull request #586 from Azure-Samples/hybrid-spa-sample
Hybrid flow application sample with Graph API client side call.
2 parents bf19edb + 42af385 commit 1eb802d

File tree

93 files changed

+76765
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

93 files changed

+76765
-0
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net6.0</TargetFramework>
5+
<Nullable>enable</Nullable>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<RootNamespace>WebApp_OpenIDConnect_DotNet</RootNamespace>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Microsoft.Identity.Client" Version="4.42.1" />
12+
<PackageReference Include="Microsoft.Identity.Web" Version="1.23.0" />
13+
<PackageReference Include="Microsoft.Identity.Web.MicrosoftGraph" Version="1.23.0" />
14+
<PackageReference Include="Microsoft.Identity.Web.UI" Version="1.23.0" />
15+
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="6.0.3" />
16+
</ItemGroup>
17+
18+
</Project>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.1.32228.430
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "2-5-HybridFlow", "2-5-HybridFlow.csproj", "{81214E3C-2B31-46D1-9EA0-067EBA3AFB2F}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Release|Any CPU = Release|Any CPU
12+
EndGlobalSection
13+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
14+
{81214E3C-2B31-46D1-9EA0-067EBA3AFB2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15+
{81214E3C-2B31-46D1-9EA0-067EBA3AFB2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
16+
{81214E3C-2B31-46D1-9EA0-067EBA3AFB2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
17+
{81214E3C-2B31-46D1-9EA0-067EBA3AFB2F}.Release|Any CPU.Build.0 = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(SolutionProperties) = preSolution
20+
HideSolutionNode = FALSE
21+
EndGlobalSection
22+
GlobalSection(ExtensibilityGlobals) = postSolution
23+
SolutionGuid = {D6A695C6-A712-4061-958E-0E8CD5D071F3}
24+
EndGlobalSection
25+
EndGlobal
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
[CmdletBinding()]
2+
param(
3+
[Parameter(Mandatory=$False, HelpMessage='Tenant ID (This is a GUID which represents the "Directory ID" of the AzureAD tenant into which you want to create the apps')]
4+
[string] $tenantId,
5+
[Parameter(Mandatory=$False, HelpMessage='Azure environment to use while running the script. Default = Global')]
6+
[string] $azureEnvironmentName
7+
)
8+
9+
Function Cleanup
10+
{
11+
if (!$azureEnvironmentName)
12+
{
13+
$azureEnvironmentName = "Global"
14+
}
15+
16+
<#
17+
.Description
18+
This function removes the Azure AD applications for the sample. These applications were created by the Configure.ps1 script
19+
#>
20+
21+
# $tenantId is the Active Directory Tenant. This is a GUID which represents the "Directory ID" of the AzureAD tenant
22+
# into which you want to create the apps. Look it up in the Azure portal in the "Properties" of the Azure AD.
23+
24+
# Connect to the Microsoft Graph API
25+
Write-Host "Connecting to Microsoft Graph"
26+
if ($tenantId -eq "") {
27+
Connect-MgGraph -Scopes "Application.ReadWrite.All" -Environment $azureEnvironmentName
28+
$tenantId = (Get-MgContext).TenantId
29+
}
30+
else {
31+
Connect-MgGraph -TenantId $tenantId -Scopes "Application.ReadWrite.All" -Environment $azureEnvironmentName
32+
}
33+
34+
# Removes the applications
35+
Write-Host "Cleaning-up applications from tenant '$tenantId'"
36+
37+
Write-Host "Removing 'HybridFlowAspNetCore' (HybridFlowAspNetCore) if needed"
38+
try
39+
{
40+
Get-MgApplication -Filter "DisplayName eq 'HybridFlowAspNetCore'" | ForEach-Object {Remove-MgApplication -ApplicationId $_.Id }
41+
}
42+
catch
43+
{
44+
Write-Host "Unable to remove the application 'HybridFlowAspNetCore' . Try deleting manually." -ForegroundColor White -BackgroundColor Red
45+
}
46+
47+
Write-Host "Making sure there are no more (HybridFlowAspNetCore) applications found, will remove if needed..."
48+
$apps = Get-MgApplication -Filter "DisplayName eq 'HybridFlowAspNetCore'"
49+
50+
if ($apps)
51+
{
52+
Remove-MgApplication -ApplicationId $apps.Id
53+
}
54+
55+
foreach ($app in $apps)
56+
{
57+
Remove-MgApplication -ApplicationId $app.Id
58+
Write-Host "Removed HybridFlowAspNetCore.."
59+
}
60+
61+
# also remove service principals of this app
62+
try
63+
{
64+
Get-MgServicePrincipal -filter "DisplayName eq 'HybridFlowAspNetCore'" | ForEach-Object {Remove-MgServicePrincipal -ApplicationId $_.Id -Confirm:$false}
65+
}
66+
catch
67+
{
68+
Write-Host "Unable to remove ServicePrincipal 'HybridFlowAspNetCore' . Try deleting manually from Enterprise applications." -ForegroundColor White -BackgroundColor Red
69+
}
70+
# remove self-signed certificate
71+
Get-ChildItem -Path Cert:\CurrentUser\My | where { $_.subject -eq "CN=HybridFlowAspNetCore" } | Remove-Item
72+
}
73+
74+
if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Applications")) {
75+
Install-Module "Microsoft.Graph.Applications" -Scope CurrentUser
76+
}
77+
Import-Module Microsoft.Graph.Applications
78+
$ErrorActionPreference = "Stop"
79+
80+
81+
Cleanup -tenantId $tenantId -environment $azureEnvironmentName
82+
83+
Write-Host "Disconnecting from tenant"
84+
Disconnect-MgGraph
85+
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
2+
[CmdletBinding()]
3+
param(
4+
[Parameter(Mandatory=$False, HelpMessage='Tenant ID (This is a GUID which represents the "Directory ID" of the AzureAD tenant into which you want to create the apps')]
5+
[string] $tenantId,
6+
[Parameter(Mandatory=$False, HelpMessage='Azure environment to use while running the script. Default = Global')]
7+
[string] $azureEnvironmentName
8+
)
9+
10+
<#
11+
This script creates the Azure AD applications needed for this sample and updates the configuration files
12+
for the visual Studio projects from the data in the Azure AD applications.
13+
14+
In case you don't have Microsoft.Graph.Applications already installed, the script will automatically install it for the current user
15+
16+
There are four ways to run this script. For more information, read the AppCreationScripts.md file in the same folder as this script.
17+
#>
18+
19+
# Adds the requiredAccesses (expressed as a pipe separated string) to the requiredAccess structure
20+
# The exposed permissions are in the $exposedPermissions collection, and the type of permission (Scope | Role) is
21+
# described in $permissionType
22+
Function AddResourcePermission($requiredAccess, `
23+
$exposedPermissions, [string]$requiredAccesses, [string]$permissionType)
24+
{
25+
foreach($permission in $requiredAccesses.Trim().Split("|"))
26+
{
27+
foreach($exposedPermission in $exposedPermissions)
28+
{
29+
if ($exposedPermission.Value -eq $permission)
30+
{
31+
$resourceAccess = New-Object Microsoft.Graph.PowerShell.Models.MicrosoftGraphResourceAccess
32+
$resourceAccess.Type = $permissionType # Scope = Delegated permissions | Role = Application permissions
33+
$resourceAccess.Id = $exposedPermission.Id # Read directory data
34+
$requiredAccess.ResourceAccess += $resourceAccess
35+
}
36+
}
37+
}
38+
}
39+
40+
#
41+
# Example: GetRequiredPermissions "Microsoft Graph" "Graph.Read|User.Read"
42+
# See also: http://stackoverflow.com/questions/42164581/how-to-configure-a-new-azure-ad-application-through-powershell
43+
Function GetRequiredPermissions([string] $applicationDisplayName, [string] $requiredDelegatedPermissions, [string]$requiredApplicationPermissions, $servicePrincipal)
44+
{
45+
# If we are passed the service principal we use it directly, otherwise we find it from the display name (which might not be unique)
46+
if ($servicePrincipal)
47+
{
48+
$sp = $servicePrincipal
49+
}
50+
else
51+
{
52+
$sp = Get-MgServicePrincipal -Filter "DisplayName eq '$applicationDisplayName'"
53+
}
54+
$appid = $sp.AppId
55+
$requiredAccess = New-Object Microsoft.Graph.PowerShell.Models.MicrosoftGraphRequiredResourceAccess
56+
$requiredAccess.ResourceAppId = $appid
57+
$requiredAccess.ResourceAccess = New-Object System.Collections.Generic.List[Microsoft.Graph.PowerShell.Models.MicrosoftGraphResourceAccess]
58+
59+
# $sp.Oauth2Permissions | Select Id,AdminConsentDisplayName,Value: To see the list of all the Delegated permissions for the application:
60+
if ($requiredDelegatedPermissions)
61+
{
62+
AddResourcePermission $requiredAccess -exposedPermissions $sp.Oauth2PermissionScopes -requiredAccesses $requiredDelegatedPermissions -permissionType "Scope"
63+
}
64+
65+
# $sp.AppRoles | Select Id,AdminConsentDisplayName,Value: To see the list of all the Application permissions for the application
66+
if ($requiredApplicationPermissions)
67+
{
68+
AddResourcePermission $requiredAccess -exposedPermissions $sp.AppRoles -requiredAccesses $requiredApplicationPermissions -permissionType "Role"
69+
}
70+
return $requiredAccess
71+
}
72+
73+
74+
Function UpdateLine([string] $line, [string] $value)
75+
{
76+
$index = $line.IndexOf(':')
77+
$lineEnd = ''
78+
79+
if($line[$line.Length - 1] -eq ','){ $lineEnd = ',' }
80+
81+
if ($index -ige 0)
82+
{
83+
$line = $line.Substring(0, $index+1) + " " + '"' + $value+ '"' + $lineEnd
84+
}
85+
return $line
86+
}
87+
88+
Function UpdateTextFile([string] $configFilePath, [System.Collections.HashTable] $dictionary)
89+
{
90+
$lines = Get-Content $configFilePath
91+
$index = 0
92+
while($index -lt $lines.Length)
93+
{
94+
$line = $lines[$index]
95+
foreach($key in $dictionary.Keys)
96+
{
97+
if ($line.Contains($key))
98+
{
99+
$lines[$index] = UpdateLine $line $dictionary[$key]
100+
}
101+
}
102+
$index++
103+
}
104+
105+
Set-Content -Path $configFilePath -Value $lines -Force
106+
}
107+
108+
109+
Function ConfigureApplications
110+
{
111+
<#.Description
112+
This function creates the Azure AD applications for the sample in the provided Azure AD tenant and updates the
113+
configuration files in the client and service project of the visual studio solution (App.Config and Web.Config)
114+
so that they are consistent with the Applications parameters
115+
#>
116+
117+
if (!$azureEnvironmentName)
118+
{
119+
$azureEnvironmentName = "Global"
120+
}
121+
122+
# Connect to the Microsoft Graph API, non-interactive is not supported for the moment (Oct 2021)
123+
Write-Host "Connecting to Microsoft Graph"
124+
if ($tenantId -eq "") {
125+
Connect-MgGraph -Scopes "Application.ReadWrite.All" -Environment $azureEnvironmentName
126+
$tenantId = (Get-MgContext).TenantId
127+
}
128+
else {
129+
Connect-MgGraph -TenantId $tenantId -Scopes "Application.ReadWrite.All" -Environment $azureEnvironmentName
130+
}
131+
132+
133+
# Create the HybridFlowAspNetCore AAD application
134+
Write-Host "Creating the AAD application (HybridFlowAspNetCore)"
135+
136+
# create the application
137+
$HybridFlowAspNetCoreAadApplication = New-MgApplication -DisplayName "HybridFlowAspNetCore" `
138+
-Web `
139+
@{ `
140+
RedirectUris = "https://localhost:7089/signin-oidc"; `
141+
ImplicitGrantSettings = @{ `
142+
EnableIdTokenIssuance=$true; `
143+
} `
144+
} `
145+
-Spa `
146+
@{ `
147+
RedirectUris = "https://localhost:7089/"; `
148+
} `
149+
-SignInAudience AzureADMyOrg `
150+
#end of command
151+
$tenantName = (Get-MgApplication -ApplicationId $HybridFlowAspNetCoreAadApplication.Id).PublisherDomain
152+
Update-MgApplication -ApplicationId $HybridFlowAspNetCoreAadApplication.Id -IdentifierUris @("https://$tenantName/HybridFlowAspNetCore")
153+
154+
# Generate a certificate
155+
Write-Host "Creating the HybridFlowAspNetCore application (HybridFlowAspNetCore)"
156+
157+
$certificateName = 'HybridFlowAspNetCore'
158+
159+
# temporarily disable the option and procees to certificate creation
160+
#$isOpenSSL = Read-Host ' By default certificate is generated using New-SelfSignedCertificate. Do you want to generate cert using OpenSSL(Y/N)?'
161+
$isOpenSSl = 'N'
162+
if($isOpenSSL -eq 'Y')
163+
{
164+
$certificate=openssl req -x509 -newkey rsa:4096 -sha256 -days 365 -keyout "$certificateName.key" -out "$certificateName.cer" -nodes -batch
165+
openssl pkcs12 -export -out "$certificateName.pfx" -inkey $certificateName.key -in "$certificateName.cer"
166+
}
167+
else
168+
{
169+
$certificate=New-SelfSignedCertificate -Subject $certificateName `
170+
-CertStoreLocation "Cert:\CurrentUser\My" `
171+
-KeyExportPolicy Exportable `
172+
-KeySpec Signature
173+
174+
$thumbprint = $certificate.Thumbprint
175+
$certificatePassword = Read-Host -Prompt "Enter password for your certificate (Please remember the password, you will need it when uploading to KeyVault): " -AsSecureString
176+
Write-Host "Exporting certificate as a PFX file"
177+
Export-PfxCertificate -Cert "Cert:\Currentuser\My\$thumbprint" -FilePath "$pwd\$certificateName.pfx" -ChainOption EndEntityCertOnly -NoProperties -Password $certificatePassword
178+
Write-Host "PFX written to:"
179+
Write-Host "$pwd\$certificateName.pfx"
180+
181+
# Add a Azure Key Credentials from the certificate for the application
182+
$HybridFlowAspNetCoreKeyCredentials = Update-MgApplication -ApplicationId $HybridFlowAspNetCoreAadApplication.Id `
183+
-KeyCredentials @(@{Type = "AsymmetricX509Cert"; Usage = "Verify"; Key= $certificate.RawData; StartDateTime = $certificate.NotBefore; EndDateTime = $certificate.NotAfter;})
184+
185+
}
186+
187+
188+
# create the service principal of the newly created application
189+
$currentAppId = $HybridFlowAspNetCoreAadApplication.AppId
190+
$HybridFlowAspNetCoreServicePrincipal = New-MgServicePrincipal -AppId $currentAppId -Tags {WindowsAzureActiveDirectoryIntegratedApp}
191+
192+
# add the user running the script as an app owner if needed
193+
$owner = Get-MgApplicationOwner -ApplicationId $HybridFlowAspNetCoreAadApplication.Id
194+
if ($owner -eq $null)
195+
{
196+
New-MgApplicationOwnerByRef -ApplicationId $HybridFlowAspNetCoreAadApplication.Id -BodyParameter = @{"@odata.id" = "htps://graph.microsoft.com/v1.0/directoryObjects/$user.ObjectId"}
197+
Write-Host "'$($user.UserPrincipalName)' added as an application owner to app '$($HybridFlowAspNetCoreServicePrincipal.DisplayName)'"
198+
}
199+
Write-Host "Done creating the HybridFlowAspNetCore application (HybridFlowAspNetCore)"
200+
201+
# URL of the AAD application in the Azure portal
202+
# Future? $HybridFlowAspNetCorePortalUrl = "https://portal.azure.com/#@"+$tenantName+"/blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/Overview/appId/"+$HybridFlowAspNetCoreAadApplication.AppId+"/objectId/"+$HybridFlowAspNetCoreAadApplication.Id+"/isMSAApp/"
203+
$HybridFlowAspNetCorePortalUrl = "https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/CallAnAPI/appId/"+$HybridFlowAspNetCoreAadApplication.AppId+"/objectId/"+$HybridFlowAspNetCoreAadApplication.Id+"/isMSAApp/"
204+
Add-Content -Value "<tr><td>HybridFlowAspNetCore</td><td>$currentAppId</td><td><a href='$HybridFlowAspNetCorePortalUrl'>HybridFlowAspNetCore</a></td></tr>" -Path createdApps.html
205+
$requiredResourcesAccess = New-Object System.Collections.Generic.List[Microsoft.Graph.PowerShell.Models.MicrosoftGraphRequiredResourceAccess]
206+
207+
208+
# Add Required Resources Access (from 'HybridFlowAspNetCore' to 'Microsoft Graph')
209+
Write-Host "Getting access from 'HybridFlowAspNetCore' to 'Microsoft Graph'"
210+
$requiredPermissions = GetRequiredPermissions -applicationDisplayName "Microsoft Graph" `
211+
-requiredDelegatedPermissions "User.Read|Contacts.Read" `
212+
213+
214+
$requiredResourcesAccess.Add($requiredPermissions)
215+
Update-MgApplication -ApplicationId $HybridFlowAspNetCoreAadApplication.Id -RequiredResourceAccess $requiredResourcesAccess
216+
Write-Host "Granted permissions."
217+
218+
# Update config file for 'HybridFlowAspNetCore'
219+
$configFile = $pwd.Path + "\..\appsettings.json"
220+
$dictionary = @{ };
221+
222+
Write-Host "Updating the sample code ($configFile)"
223+
224+
UpdateTextFile -configFilePath $configFile -dictionary $dictionary
225+
226+
$appSettingsObject = (Get-Content ..\appsettings.json | ConvertFrom-Json)
227+
228+
# JSON is auto-generated.
229+
$appSettingsObject.AzureAd = ConvertFrom-Json '{"Instance":"https://login.microsoftonline.com/","Domain":"Auto","TenantId":"Auto","ClientId":"Auto","CallbackPath":"/signin-oidc","WithSpaAuthCode":true,"ClientCertificates":[{"SourceType":"StoreWithDistinguishedName","CertificateStorePath":"CurrentUser/My","CertificateDistinguishedName":"CN=HybridFlowAspNetCore"}]}';
230+
231+
$appSettingsObject.AzureAd.TenantId = $tenantId;
232+
$appSettingsObject.AzureAd.ClientId = $currentAppId;
233+
$appSettingsObject.AzureAd.Domain = $tenantName;
234+
235+
# JSON is auto-generated.
236+
$appSettingsObject.DownStreamApi = ConvertFrom-Json '{"BaseUrl":"https://graph.microsoft.com/v1.0","Scopes":"User.Read Contacts.Read"}';
237+
238+
Write-Host "Updating the appsetings.json file at '..\appsettings.json'"
239+
$appSettingsObject | ConvertTo-Json -Depth 3 | Out-File ..\appsettings.json
240+
241+
if($isOpenSSL -eq 'Y')
242+
{
243+
Write-Host -ForegroundColor Green "------------------------------------------------------------------------------------------------"
244+
Write-Host "You have generated certificate using OpenSSL so follow below steps: "
245+
Write-Host "Install the certificate on your system from current folder."
246+
Write-Host -ForegroundColor Green "------------------------------------------------------------------------------------------------"
247+
}
248+
Add-Content -Value "</tbody></table></body></html>" -Path createdApps.html
249+
}
250+
251+
# Pre-requisites
252+
if ($null -eq (Get-Module -ListAvailable -Name "Microsoft.Graph.Applications")) {
253+
Install-Module "Microsoft.Graph.Applications" -Scope CurrentUser
254+
}
255+
256+
Import-Module Microsoft.Graph.Applications
257+
258+
Set-Content -Value "<html><body><table>" -Path createdApps.html
259+
Add-Content -Value "<thead><tr><th>Application</th><th>AppId</th><th>Url in the Azure portal</th></tr></thead><tbody>" -Path createdApps.html
260+
261+
$ErrorActionPreference = "Stop"
262+
263+
# Run interactively (will ask you for the tenant ID)
264+
ConfigureApplications -tenantId $tenantId -environment $azureEnvironmentName
265+
266+
Write-Host "Disconnecting from tenant"
267+
Disconnect-MgGraph

0 commit comments

Comments
 (0)