Skip to content

Commit fd9f17d

Browse files
author
John Luo
authored
Update blazor templates with Microsoft.Identity.Web APIs (#24268)
* Update blazor and components web templates * Update Identity.Web version to 0.2.1-preview
1 parent 8062d94 commit fd9f17d

32 files changed

+936
-143
lines changed

eng/Versions.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,8 @@
247247
<IdentityServer4StoragePackageVersion>3.0.0</IdentityServer4StoragePackageVersion>
248248
<IdentityServer4EntityFrameworkStoragePackageVersion>3.0.0</IdentityServer4EntityFrameworkStoragePackageVersion>
249249
<MessagePackPackageVersion>2.1.90</MessagePackPackageVersion>
250-
<MicrosoftIdentityWebPackageVersion>0.2.0-preview</MicrosoftIdentityWebPackageVersion>
251-
<MicrosoftIdentityWebUIPackageVersion>0.2.0-preview</MicrosoftIdentityWebUIPackageVersion>
250+
<MicrosoftIdentityWebPackageVersion>0.2.1-preview</MicrosoftIdentityWebPackageVersion>
251+
<MicrosoftIdentityWebUIPackageVersion>0.2.1-preview</MicrosoftIdentityWebUIPackageVersion>
252252
<MicrosoftGraphPackageVersion>3.8.0</MicrosoftGraphPackageVersion>
253253
<MessagePackAnalyzerPackageVersion>$(MessagePackPackageVersion)</MessagePackAnalyzerPackageVersion>
254254
<MoqPackageVersion>4.10.0</MoqPackageVersion>

src/ProjectTemplates/BlazorTemplates.Tests/BlazorServerTemplateTest.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,5 +188,30 @@ private void TestBasicNavigation()
188188
Browser.Exists(By.CssSelector("table>tbody>tr"));
189189
Browser.Equal(5, () => Browser.FindElements(By.CssSelector("p+table>tbody>tr")).Count);
190190
}
191+
192+
[Theory]
193+
[QuarantinedTest]
194+
[InlineData("IndividualB2C", null)]
195+
[InlineData("IndividualB2C", new string[] { "--called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })]
196+
[InlineData("SingleOrg", null)]
197+
[InlineData("SingleOrg", new string[] { "--called-api-url \"https://graph.microsoft.com\"", "--called-api-scopes user.readwrite" })]
198+
[InlineData("SingleOrg", new string[] { "--calls-graph" })]
199+
public async Task BlazorServerTemplat_IdentityWeb_BuildAndPublish(string auth, string[] args)
200+
{
201+
Project = await ProjectFactory.GetOrCreateProject("blazorserveridweb" + Guid.NewGuid().ToString().Substring(0, 10).ToLower(), Output);
202+
203+
var createResult = await Project.RunDotNetNewAsync("blazorserver", auth: auth, args: args);
204+
Assert.True(0 == createResult.ExitCode, ErrorMessages.GetFailedProcessMessage("create/restore", Project, createResult));
205+
206+
var publishResult = await Project.RunDotNetPublishAsync();
207+
Assert.True(0 == publishResult.ExitCode, ErrorMessages.GetFailedProcessMessage("publish", Project, publishResult));
208+
209+
// Run dotnet build after publish. The reason is that one uses Config = Debug and the other uses Config = Release
210+
// The output from publish will go into bin/Release/netcoreappX.Y/publish and won't be affected by calling build
211+
// later, while the opposite is not true.
212+
213+
var buildResult = await Project.RunDotNetBuildAsync();
214+
Assert.True(0 == buildResult.ExitCode, ErrorMessages.GetFailedProcessMessage("build", Project, buildResult));
215+
}
191216
}
192217
}

src/ProjectTemplates/BlazorTemplates.Tests/BlazorWasmTemplateTest.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,27 @@ public async Task BlazorWasmStandaloneTemplate_IndividualAuth_Works()
418418
"--default-scope", "full",
419419
"--app-id-uri", "ApiUri",
420420
"--api-client-id", "1234123413241324"),
421+
new TemplateInstance(
422+
"blazorwasmhostedaadgraph", "-ho",
423+
"-au", "SingleOrg",
424+
"--calls-graph",
425+
"--domain", "my-domain",
426+
"--tenant-id", "tenantId",
427+
"--client-id", "clientId",
428+
"--default-scope", "full",
429+
"--app-id-uri", "ApiUri",
430+
"--api-client-id", "1234123413241324"),
431+
new TemplateInstance(
432+
"blazorwasmhostedaadapi", "-ho",
433+
"-au", "SingleOrg",
434+
"--called-api-url", "\"https://graph.microsoft.com\"",
435+
"--called-api-scopes", "user.readwrite",
436+
"--domain", "my-domain",
437+
"--tenant-id", "tenantId",
438+
"--client-id", "clientId",
439+
"--default-scope", "full",
440+
"--app-id-uri", "ApiUri",
441+
"--api-client-id", "1234123413241324"),
421442
new TemplateInstance(
422443
"blazorwasmstandaloneaadb2c",
423444
"-au", "IndividualB2C",

src/ProjectTemplates/Web.ProjectTemplates/BlazorServerWeb-CSharp.csproj.in

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,15 @@
1717
<!--#endif -->
1818
<!--#if (IndividualAuth || OrganizationalAuth) -->
1919
<ItemGroup>
20-
<PackageReference Include="Microsoft.AspNetCore.Authentication.AzureAD.UI" Version="${MicrosoftAspNetCoreAuthenticationAzureADUIPackageVersion}" Condition="'$(OrganizationalAuth)' == 'True'" />
21-
<PackageReference Include="Microsoft.AspNetCore.Authentication.AzureADB2C.UI" Version="${MicrosoftAspNetCoreAuthenticationAzureADB2CUIPackageVersion}" Condition="'$(IndividualB2CAuth)' == 'True'" />
2220
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="${MicrosoftAspNetCoreDiagnosticsEntityFrameworkCorePackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' " />
2321
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="${MicrosoftAspNetCoreIdentityEntityFrameworkCorePackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' " />
2422
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="${MicrosoftAspNetCoreIdentityUIPackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' " />
2523
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="${MicrosoftEntityFrameworkCoreSqlServerPackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' AND '$(UseLocalDB)' == 'True'" />
2624
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="${MicrosoftEntityFrameworkCoreSqlitePackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' AND '$(UseLocalDB)' != 'True'" />
2725
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="${MicrosoftEntityFrameworkCoreToolsPackageVersion}" Condition=" '$(IndividualLocalAuth)' == 'True' " />
26+
<PackageReference Include="Microsoft.Identity.Web" Version="${MicrosoftIdentityWebPackageVersion}" Condition=" '$(IndividualB2CAuth)' == 'True' OR '$(OrganizationalAuth)' == 'True'" />
27+
<PackageReference Include="Microsoft.Identity.Web.UI" Version="${MicrosoftIdentityWebUIPackageVersion}" Condition=" '$(IndividualB2CAuth)' == 'True' OR '$(OrganizationalAuth)' == 'True'" />
28+
<PackageReference Include="Microsoft.Graph" Version="${MicrosoftGraphPackageVersion}" Condition=" '$(GenerateGraph)' == 'True' "/>
2829
</ItemGroup>
2930

3031
<!--#endif -->

src/ProjectTemplates/Web.ProjectTemplates/ComponentsWebAssembly-CSharp.Server.csproj.in

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@
3838
<!--#endif -->
3939
<!--#if (OrganizationalAuth || IndividualB2CAuth) -->
4040
<ItemGroup>
41-
<PackageReference Include="Microsoft.AspNetCore.Authentication.AzureAD.UI" Version="${MicrosoftAspNetCoreAuthenticationAzureADUIPackageVersion}" Condition="'$(OrganizationalAuth)' == 'True'" />
42-
<PackageReference Include="Microsoft.AspNetCore.Authentication.AzureADB2C.UI" Version="${MicrosoftAspNetCoreAuthenticationAzureADB2CUIPackageVersion}" Condition="'$(IndividualB2CAuth)' == 'True'" />
41+
<PackageReference Include="Microsoft.Identity.Web" Version="${MicrosoftIdentityWebPackageVersion}" />
42+
<PackageReference Include="Microsoft.Identity.Web.UI" Version="${MicrosoftIdentityWebUIPackageVersion}" />
43+
<PackageReference Include="Microsoft.Graph" Version="${MicrosoftGraphPackageVersion}" Condition=" '$(GenerateGraph)' == 'True' "/>
4344
</ItemGroup>
4445
<!--#endif -->
4546

src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/dotnetcli.host.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,18 @@
6767
"NoHttps": {
6868
"longName": "no-https",
6969
"shortName": ""
70+
},
71+
"CalledApiUrl": {
72+
"longName": "called-api-url",
73+
"shortName": ""
74+
},
75+
"CalledApiScopes": {
76+
"longName": "called-api-scopes",
77+
"shortName": ""
78+
},
79+
"CallsMicrosoftGraph": {
80+
"longName": "calls-graph",
81+
"shortName": ""
7082
}
7183
},
7284
"usageExamples": [

src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/.template.config/template.json

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,52 @@
127127
"Shared/LoginDisplay.IndividualB2CAuth.razor",
128128
"Shared/LoginDisplay.OrganizationalAuth.razor"
129129
]
130+
},
131+
{
132+
"condition": "(!GenerateApi)",
133+
"exclude": [
134+
"Services/DownstreamWebApi.cs",
135+
"Pages/CallWebApi.razor"
136+
]
137+
},
138+
{
139+
"condition": "(!GenerateGraph)",
140+
"exclude": [
141+
"Services/MicrosoftGraphServiceExtensions.cs",
142+
"Services/TokenAcquisitionCredentialProvider.cs",
143+
"Shared/NavMenu.CallsMicrosoftGraph.razor",
144+
"Pages/ShowProfile.razor"
145+
]
146+
},
147+
{
148+
"condition": "(!GenerateApiOrGraph)",
149+
"rename": {
150+
"Shared/NavMenu.NoGraphOrApi.razor": "Shared/NavMenu.razor"
151+
},
152+
"exclude": [
153+
"Shared/NavMenu.CallsMicrosoftGraph.razor",
154+
"Shared/NavMenu.CallsWebApi.razor"
155+
]
156+
},
157+
{
158+
"condition": "(GenerateGraph)",
159+
"rename": {
160+
"Shared/NavMenu.CallsMicrosoftGraph.razor": "Shared/NavMenu.razor"
161+
},
162+
"exclude": [
163+
"Shared/NavMenu.NoGraphOrApi.razor",
164+
"Shared/NavMenu.CallsWebApi.razor"
165+
]
166+
},
167+
{
168+
"condition": "(GenerateApi)",
169+
"rename": {
170+
"Shared/NavMenu.CallsWebApi.razor": "Shared/NavMenu.razor"
171+
},
172+
"exclude": [
173+
"Shared/NavMenu.NoGraphOrApi.razor",
174+
"Shared/NavMenu.CallsMicrosoftGraph.razor"
175+
]
130176
}
131177
]
132178
}
@@ -174,21 +220,28 @@
174220
"SignUpSignInPolicyId": {
175221
"type": "parameter",
176222
"datatype": "string",
177-
"defaultValue": "",
223+
"defaultValue": "b2c_1_susi",
178224
"replaces": "MySignUpSignInPolicyId",
179225
"description": "The sign-in and sign-up policy ID for this project (use with IndividualB2C auth)."
180226
},
227+
"SignedOutCallbackPath": {
228+
"type": "parameter",
229+
"datatype": "string",
230+
"defaultValue": "/signout/B2C_1_susi",
231+
"replaces": "/signout/MySignUpSignInPolicyId",
232+
"description": "The global signout callback (use with IndividualB2C auth)."
233+
},
181234
"ResetPasswordPolicyId": {
182235
"type": "parameter",
183236
"datatype": "string",
184-
"defaultValue": "",
237+
"defaultValue": "b2c_1_reset",
185238
"replaces": "MyResetPasswordPolicyId",
186239
"description": "The reset password policy ID for this project (use with IndividualB2C auth)."
187240
},
188241
"EditProfilePolicyId": {
189242
"type": "parameter",
190243
"datatype": "string",
191-
"defaultValue": "",
244+
"defaultValue": "b2c_1_edit_profile",
192245
"replaces": "MyEditProfilePolicyId",
193246
"description": "The edit profile policy ID for this project (use with IndividualB2C auth)."
194247
},
@@ -352,6 +405,37 @@
352405
"format": "yyyy"
353406
}
354407
},
408+
"CalledApiUrl": {
409+
"type": "parameter",
410+
"datatype": "string",
411+
"replaces": "[WebApiUrl]",
412+
"defaultValue" : "https://graph.microsoft.com/beta",
413+
"description": "URL of the API to call from the web app. This option only applies if --auth SingleOrg, --auth MultiOrg or --auth IndividualB2C is specified."
414+
},
415+
"CallsMicrosoftGraph": {
416+
"type": "parameter",
417+
"datatype": "bool",
418+
"defaultValue": "false",
419+
"description": "Specifies if the web app calls Microsoft Graph. This option only applies if --auth SingleOrg or --auth MultiOrg is specified."
420+
},
421+
"CalledApiScopes": {
422+
"type": "parameter",
423+
"datatype": "string",
424+
"replaces" : "user.read",
425+
"description": "Scopes to request to call the API from the web app. This option only applies if --auth SingleOrg, --auth MultiOrg or --auth IndividualB2C is specified."
426+
},
427+
"GenerateApi": {
428+
"type": "computed",
429+
"value": "((IndividualB2CAuth || OrganizationalAuth) && (CalledApiUrl != \"https://graph.microsoft.com/beta\" || CalledApiScopes != \"user.read\"))"
430+
},
431+
"GenerateGraph": {
432+
"type": "computed",
433+
"value": "(OrganizationalAuth && CallsMicrosoftGraph)"
434+
},
435+
"GenerateApiOrGraph": {
436+
"type": "computed",
437+
"value": "(GenerateApi || GenerateGraph)"
438+
},
355439
"skipRestore": {
356440
"type": "parameter",
357441
"datatype": "bool",

src/ProjectTemplates/Web.ProjectTemplates/content/BlazorServerWeb-CSharp/Areas/Identity/Pages/Shared/_LoginPartial.cshtml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,21 @@
77
@if (SignInManager.IsSignedIn(User))
88
{
99
<li class="nav-item">
10-
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Manage/Index" title="Manage">Hello @User.Identity.Name!</a>
10+
<a class="nav-link text-dark" asp-area="MicrosoftIdentity" asp-page="/Account/Manage/Index" title="Manage">Hello @User.Identity.Name!</a>
1111
</li>
1212
<li class="nav-item">
13-
<form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="/" method="post">
13+
<form class="form-inline" asp-area="MicrosoftIdentity" asp-page="/Account/Logout" asp-route-returnUrl="/" method="post">
1414
<button type="submit" class="nav-link btn btn-link text-dark">Logout</button>
1515
</form>
1616
</li>
1717
}
1818
else
1919
{
2020
<li class="nav-item">
21-
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Register">Register</a>
21+
<a class="nav-link text-dark" asp-area="MicrosoftIdentity" asp-page="/Account/Register">Register</a>
2222
</li>
2323
<li class="nav-item">
24-
<a class="nav-link text-dark" asp-area="Identity" asp-page="/Account/Login">Login</a>
24+
<a class="nav-link text-dark" asp-area="MicrosoftIdentity" asp-page="/Account/Login">Login</a>
2525
</li>
2626
}
2727
</ul>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
@page "/callwebapi"
2+
3+
@using BlazorServerWeb_CSharp
4+
@using Microsoft.Identity.Web
5+
6+
@inject IDownstreamWebApi downstreamAPI
7+
@inject MicrosoftIdentityConsentAndConditionalAccessHandler ConsentHandler
8+
9+
<h1>Call an API</h1>
10+
11+
<p>This component demonstrates fetching data from a Web API.</p>
12+
13+
@if (apiResult == null)
14+
{
15+
<p><em>Loading...</em></p>
16+
}
17+
else
18+
{
19+
<h2>API Result</h2>
20+
@apiResult
21+
}
22+
23+
@code {
24+
private string apiResult;
25+
26+
protected override async Task OnInitializedAsync()
27+
{
28+
try
29+
{
30+
apiResult = await downstreamAPI.CallWebApiAsync("me");
31+
}
32+
catch (Exception ex)
33+
{
34+
ConsentHandler.HandleException(ex);
35+
}
36+
}
37+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
@page "/showprofile"
2+
3+
@using Microsoft.Identity.Web
4+
@using Microsoft.Graph
5+
@inject Microsoft.Graph.GraphServiceClient GraphServiceClient
6+
@inject MicrosoftIdentityConsentAndConditionalAccessHandler ConsentHandler
7+
8+
<h1>Me</h1>
9+
10+
<p>This component demonstrates fetching data from a service.</p>
11+
12+
@if (user == null)
13+
{
14+
<p><em>Loading...</em></p>
15+
}
16+
else
17+
{
18+
<table class="table table-striped table-condensed" style="font-family: monospace">
19+
<tr>
20+
<th>Property</th>
21+
<th>Value</th>
22+
</tr>
23+
<tr>
24+
<td>Name</td>
25+
<td>@user.DisplayName</td>
26+
</tr>
27+
<tr>
28+
<td>Photo</td>
29+
<td>
30+
@{
31+
if (photo != null)
32+
{
33+
<img style="margin: 5px 0; width: 150px" src="data:image/jpeg;base64, @photo" />
34+
}
35+
else
36+
{
37+
<h3>NO PHOTO</h3>
38+
<p>Check user profile in Azure Active Directory to add a photo.</p>
39+
}
40+
}
41+
</td>
42+
</tr>
43+
</table>
44+
}
45+
46+
@code {
47+
User user;
48+
string photo;
49+
50+
protected override async Task OnInitializedAsync()
51+
{
52+
try
53+
{
54+
user = await GraphServiceClient.Me.Request().GetAsync();
55+
photo = await GetPhoto();
56+
}
57+
catch (Exception ex)
58+
{
59+
ConsentHandler.HandleException(ex);
60+
}
61+
}
62+
63+
protected async Task<string> GetPhoto()
64+
{
65+
string photo;
66+
67+
try
68+
{
69+
using (var photoStream = await GraphServiceClient.Me.Photo.Content.Request().GetAsync())
70+
{
71+
byte[] photoByte = ((System.IO.MemoryStream)photoStream).ToArray();
72+
photo = Convert.ToBase64String(photoByte);
73+
this.StateHasChanged();
74+
}
75+
76+
}
77+
catch (Exception)
78+
{
79+
photo = null;
80+
}
81+
return photo;
82+
}
83+
84+
}

0 commit comments

Comments
 (0)