Skip to content

Commit 4e095b2

Browse files
authored
Merge pull request #25 from damienbod/dev-cae-net6-features
CAE and .NET6 features
2 parents 7b4f8af + 3e4aa64 commit 4e095b2

27 files changed

+590
-237
lines changed

Changelog.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
[Readme](https://github.com/damienbod/Blazor.BFF.AzureB2C.Template/blob/main/README.md)
44

5+
**2022-03-20** 1.2.0
6+
- use new top-level statements and remove
7+
- enable ImplicitUsings
8+
- add IAntiforgeryHttpClientFactory/AntiforgeryHttpClientFactory
9+
- Replace of IdentityModel with System.Security.Claims and remove IdentityModel nuget package
10+
- Add support for Azure AD Continuous Access Evaluation CAE
11+
- Add 404, 401, response handling
12+
- Update nuget packages
13+
514
**2022-03-20** 1.1.0
615
- Updated nuget packages
716
- Using nullable enabled

README-NUGET.md

Lines changed: 107 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ This template can be used to create a Blazor WASM application hosted in an ASP.N
1010
- BFF with Azure B2C using Microsoft.Identity.Web
1111
- OAuth2 and OpenID Connect OIDC
1212
- No tokens in the browser
13+
- Azure AD Continuous Access Evaluation CAE support
1314

1415
## Using the template
1516

@@ -62,6 +63,112 @@ Add the permissions for Microsoft Graph if required, application scopes are used
6263
},
6364
```
6465

66+
### Use Continuous Access Evaluation CAE with a downstream API (access_token)
67+
68+
#### Azure app registration manifest
69+
70+
```json
71+
"optionalClaims": {
72+
"idToken": [],
73+
"accessToken": [
74+
{
75+
"name": "xms_cc",
76+
"source": null,
77+
"essential": false,
78+
"additionalProperties": []
79+
}
80+
],
81+
"saml2Token": []
82+
},
83+
```
84+
85+
Any API call for the Blazor WASM could be implemented like this:
86+
87+
```
88+
[HttpGet]
89+
public async Task<IActionResult> Get()
90+
{
91+
try
92+
{
93+
// Do logic which calls an API and throws claims challenge
94+
// WebApiMsalUiRequiredException. The WWW-Authenticate header is set
95+
// using the OpenID Connect standards and Signals spec.
96+
}
97+
catch (WebApiMsalUiRequiredException hex)
98+
{
99+
var claimChallenge = WwwAuthenticateParameters
100+
.GetClaimChallengeFromResponseHeaders(hex.Headers);
101+
102+
return Unauthorized(claimChallenge);
103+
}
104+
}
105+
```
106+
107+
The downstream API call could be implemented something like this:
108+
109+
```
110+
public async Task<T> CallApiAsync(string url)
111+
{
112+
var client = _clientFactory.CreateClient();
113+
114+
// ... add bearer token
115+
116+
var response = await client.GetAsync(url);
117+
if (response.IsSuccessStatusCode)
118+
{
119+
var stream = await response.Content.ReadAsStreamAsync();
120+
var payload = await JsonSerializer.DeserializeAsync<T>(stream);
121+
122+
return payload;
123+
}
124+
125+
// You can check the WWW-Authenticate header first, if it is a CAE challenge
126+
127+
throw new WebApiMsalUiRequiredException($"Error: {response.StatusCode}.", response);
128+
}
129+
```
130+
131+
### Use Continuous Access Evaluation CAE in a standalone app (id_token)
132+
133+
#### Azure app registration manifest
134+
135+
```json
136+
"optionalClaims": {
137+
"idToken": [
138+
{
139+
"name": "xms_cc",
140+
"source": null,
141+
"essential": false,
142+
"additionalProperties": []
143+
}
144+
],
145+
"accessToken": [],
146+
"saml2Token": []
147+
},
148+
```
149+
If using a CAE Authcontext in a standalone project, you only need to challenge against the claims in the application.
150+
151+
```
152+
private readonly CaeClaimsChallengeService _caeClaimsChallengeService;
153+
154+
public AdminApiCallsController(CaeClaimsChallengeService caeClaimsChallengeService)
155+
{
156+
_caeClaimsChallengeService = caeClaimsChallengeService;
157+
}
158+
159+
[HttpGet]
160+
public IActionResult Get()
161+
{
162+
// if CAE claim missing in id token, the required claims challenge is returned
163+
var claimsChallenge = _caeClaimsChallengeService
164+
.CheckForRequiredAuthContextIdToken(AuthContextId.C1, HttpContext);
165+
166+
if (claimsChallenge != null)
167+
{
168+
return Unauthorized(claimsChallenge);
169+
}
170+
```
171+
65172
### uninstall
66173

67174
```
@@ -72,7 +179,6 @@ dotnet new -u Blazor.BFF.AzureB2C.Template
72179
## Credits, Used NuGet packages + ASP.NET Core 6.0 standard packages
73180

74181
- NetEscapades.AspNetCore.SecurityHeaders
75-
- IdentityModel
76182

77183
## Links
78184

README.md

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ This template can be used to create a Blazor WASM application hosted in an ASP.N
1212
- BFF with Azure B2C using Microsoft.Identity.Web
1313
- OAuth2 and OpenID Connect OIDC
1414
- No tokens in the browser
15+
- - Azure AD Continuous Access Evaluation CAE support
1516

1617
## Other templates
1718

@@ -70,6 +71,112 @@ Add the permissions for Microsoft Graph if required, application scopes are used
7071
},
7172
```
7273

74+
### Use Continuous Access Evaluation CAE with a downstream API (access_token)
75+
76+
#### Azure app registration manifest
77+
78+
```json
79+
"optionalClaims": {
80+
"idToken": [],
81+
"accessToken": [
82+
{
83+
"name": "xms_cc",
84+
"source": null,
85+
"essential": false,
86+
"additionalProperties": []
87+
}
88+
],
89+
"saml2Token": []
90+
},
91+
```
92+
93+
Any API call for the Blazor WASM could be implemented like this:
94+
95+
```
96+
[HttpGet]
97+
public async Task<IActionResult> Get()
98+
{
99+
try
100+
{
101+
// Do logic which calls an API and throws claims challenge
102+
// WebApiMsalUiRequiredException. The WWW-Authenticate header is set
103+
// using the OpenID Connect standards and Signals spec.
104+
}
105+
catch (WebApiMsalUiRequiredException hex)
106+
{
107+
var claimChallenge = WwwAuthenticateParameters
108+
.GetClaimChallengeFromResponseHeaders(hex.Headers);
109+
110+
return Unauthorized(claimChallenge);
111+
}
112+
}
113+
```
114+
115+
The downstream API call could be implemented something like this:
116+
117+
```
118+
public async Task<T> CallApiAsync(string url)
119+
{
120+
var client = _clientFactory.CreateClient();
121+
122+
// ... add bearer token
123+
124+
var response = await client.GetAsync(url);
125+
if (response.IsSuccessStatusCode)
126+
{
127+
var stream = await response.Content.ReadAsStreamAsync();
128+
var payload = await JsonSerializer.DeserializeAsync<T>(stream);
129+
130+
return payload;
131+
}
132+
133+
// You can check the WWW-Authenticate header first, if it is a CAE challenge
134+
135+
throw new WebApiMsalUiRequiredException($"Error: {response.StatusCode}.", response);
136+
}
137+
```
138+
139+
### Use Continuous Access Evaluation CAE in a standalone app (id_token)
140+
141+
#### Azure app registration manifest
142+
143+
```json
144+
"optionalClaims": {
145+
"idToken": [
146+
{
147+
"name": "xms_cc",
148+
"source": null,
149+
"essential": false,
150+
"additionalProperties": []
151+
}
152+
],
153+
"accessToken": [],
154+
"saml2Token": []
155+
},
156+
```
157+
If using a CAE Authcontext in a standalone project, you only need to challenge against the claims in the application.
158+
159+
```
160+
private readonly CaeClaimsChallengeService _caeClaimsChallengeService;
161+
162+
public AdminApiCallsController(CaeClaimsChallengeService caeClaimsChallengeService)
163+
{
164+
_caeClaimsChallengeService = caeClaimsChallengeService;
165+
}
166+
167+
[HttpGet]
168+
public IActionResult Get()
169+
{
170+
// if CAE claim missing in id token, the required claims challenge is returned
171+
var claimsChallenge = _caeClaimsChallengeService
172+
.CheckForRequiredAuthContextIdToken(AuthContextId.C1, HttpContext);
173+
174+
if (claimsChallenge != null)
175+
{
176+
return Unauthorized(claimsChallenge);
177+
}
178+
```
179+
73180
### uninstall
74181

75182
```
@@ -91,7 +198,7 @@ nuget pack content/Blazor.BFF.AzureB2C.Template.nuspec
91198
Locally built nupkg:
92199

93200
```
94-
dotnet new -i Blazor.BFF.AzureB2C.Template.1.1.0.nupkg
201+
dotnet new -i Blazor.BFF.AzureB2C.Template.1.2.0.nupkg
95202
```
96203

97204
Local folder:
@@ -111,7 +218,6 @@ https://docs.microsoft.com/en-us/azure/active-directory-b2c/tutorial-register-ap
111218
## Credits, Used NuGet packages + ASP.NET Core 6.0 standard packages
112219

113220
- NetEscapades.AspNetCore.SecurityHeaders
114-
- IdentityModel.AspNetCore
115221

116222
## Links
117223

content/Blazor.BFF.AzureB2C.Template.nuspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
33
<metadata>
44
<id>Blazor.BFF.AzureB2C.Template</id>
5-
<version>1.1.0</version>
5+
<version>1.2.0</version>
66
<title>Blazor.BFF.AzureB2C.Template</title>
77
<license type="file">LICENSE</license>
88
<description>Blazor BFF template for WASM ASP.NET Core hosted</description>
@@ -15,7 +15,7 @@
1515
<requireLicenseAcceptance>false</requireLicenseAcceptance>
1616
<copyright>2022 damienbod</copyright>
1717
<summary>This template provides a simple Blazor template with BFF server authentication WASM hosted</summary>
18-
<releaseNotes>Updated nuget packages, using nullable enabled</releaseNotes>
18+
<releaseNotes>use new top-level statements and remove, enable ImplicitUsings, add IAntiforgeryHttpClientFactory/AntiforgeryHttpClientFactory, Replace of IdentityModel with System.Security.Claims and remove IdentityModel nuget package, Add support for Azure AD Continuous Access Evaluation CAE, Add 404, 401, response handling, Update nuget packages</releaseNotes>
1919
<repository type="git" url="https://github.com/damienbod/Blazor.BFF.AzureB2C.Template" />
2020
<packageTypes>
2121
<packageType name="Template" />

content/BlazorBffAzureB2C/Client/BlazorBffAzureB2C.Client.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@
44
<TargetFramework>net6.0</TargetFramework>
55
<NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
66
<Nullable>enable</Nullable>
7+
<ImplicitUsings>enable</ImplicitUsings>
78
</PropertyGroup>
89

910
<ItemGroup>
10-
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.3" />
11-
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.3" PrivateAssets="all" />
11+
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.5" />
12+
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.5" PrivateAssets="all" />
1213
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
13-
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="6.0.3" />
14+
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="6.0.5" />
1415
</ItemGroup>
1516

1617
<ItemGroup>
1718
<ProjectReference Include="..\Shared\BlazorBffAzureB2C.Shared.csproj" />
1819
</ItemGroup>
19-
2020
</Project>

content/BlazorBffAzureB2C/Client/Pages/DirectApi.razor

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
@page "/directapi"
2-
@inject IHttpClientFactory httpClientFactory
2+
@inject IAntiforgeryHttpClientFactory httpClientFactory
33
@inject IJSRuntime JSRuntime
44

55
<h1>Data from Direct API</h1>
@@ -28,16 +28,12 @@ else
2828
}
2929

3030
@code {
31-
private string[] apiData;
31+
private string[]? apiData;
3232

3333
protected override async Task OnInitializedAsync()
3434
{
35-
var token = await JSRuntime.InvokeAsync<string>("getAntiForgeryToken");
36-
37-
var client = httpClientFactory.CreateClient("authorizedClient");
38-
client.DefaultRequestHeaders.Add("X-XSRF-TOKEN", token);
35+
var client = await httpClientFactory.CreateClientAsync();
3936

4037
apiData = await client.GetFromJsonAsync<string[]>("api/DirectApi");
4138
}
42-
4339
}

content/BlazorBffAzureB2C/Client/Pages/GraphApiCall.razor

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
@page "/graphapicall"
2-
@inject IHttpClientFactory httpClientFactory
2+
@inject IAntiforgeryHttpClientFactory httpClientFactory
33
@inject IJSRuntime JSRuntime
44

55
<h1>Data from Graph API</h1>
@@ -28,16 +28,12 @@ else
2828
}
2929

3030
@code {
31-
private string[] apiData;
31+
private string[]? apiData;
3232

3333
protected override async Task OnInitializedAsync()
3434
{
35-
var token = await JSRuntime.InvokeAsync<string>("getAntiForgeryToken");
36-
37-
var client = httpClientFactory.CreateClient("authorizedClient");
38-
client.DefaultRequestHeaders.Add("X-XSRF-TOKEN", token);
35+
var client = await httpClientFactory.CreateClientAsync();
3936

4037
apiData = await client.GetFromJsonAsync<string[]>("api/GraphApiCalls");
4138
}
42-
4339
}

0 commit comments

Comments
 (0)