From e62cff5b6269efad4a552a9d03e8069d66078885 Mon Sep 17 00:00:00 2001
From: guardrex <1622880+guardrex@users.noreply.github.com>
Date: Fri, 24 Jul 2026 13:01:34 -0400
Subject: [PATCH 01/17] Add custom policy provider example to authz BWA
---
.../Components/Pages/Home.razor | 11 ++++++
.../Pages/PassMinimumAge21Policy.razor | 12 +++++++
.../PassMinimumAge21PolicyWithAttribute.razor | 7 ++++
.../MinimumAgeAuthorizeAttribute.cs | 27 +++++++++++++++
.../Providers/MinimumAgePolicyProvider.cs | 34 +++++++++++++++++++
.../BlazorWebAppAuthorization/Program.cs | 2 ++
.../BlazorWebAppAuthorization/README.md | 6 ++--
7 files changed, 96 insertions(+), 3 deletions(-)
create mode 100644 security/authorization/BlazorWebAppAuthorization/Components/Pages/PassMinimumAge21Policy.razor
create mode 100644 security/authorization/BlazorWebAppAuthorization/Components/Pages/PassMinimumAge21PolicyWithAttribute.razor
create mode 100644 security/authorization/BlazorWebAppAuthorization/Policies/Attributes/MinimumAgeAuthorizeAttribute.cs
create mode 100644 security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs
diff --git a/security/authorization/BlazorWebAppAuthorization/Components/Pages/Home.razor b/security/authorization/BlazorWebAppAuthorization/Components/Pages/Home.razor
index 55f347f4..bbbc59a3 100644
--- a/security/authorization/BlazorWebAppAuthorization/Components/Pages/Home.razor
+++ b/security/authorization/BlazorWebAppAuthorization/Components/Pages/Home.razor
@@ -74,6 +74,17 @@
Pass 'CustomerServiceMember' and 'HumanResourcesMember' policies with [Authorize] attributes
+
Policy provider authorization example
+
+
+ Pass 'MinimumAge21' Policy
+
+
+
+
+ Pass 'MinimumAge21' Policy (attribute-based)
+
+
Sign into the app with one of the seeded accounts. See the README file for accounts and credentials.
diff --git a/security/authorization/BlazorWebAppAuthorization/Components/Pages/PassMinimumAge21Policy.razor b/security/authorization/BlazorWebAppAuthorization/Components/Pages/PassMinimumAge21Policy.razor
new file mode 100644
index 00000000..96472fd4
--- /dev/null
+++ b/security/authorization/BlazorWebAppAuthorization/Components/Pages/PassMinimumAge21Policy.razor
@@ -0,0 +1,12 @@
+@page "/pass-minimumage21-policy"
+
+Pass 'MinimumAge21' policy with AuthorizeView
+
+
+
+ You satisfy the 'MinimumAge21' policy.
+
+
+ You don't satisfy the 'MinimumAge21' policy.
+
+
diff --git a/security/authorization/BlazorWebAppAuthorization/Components/Pages/PassMinimumAge21PolicyWithAttribute.razor b/security/authorization/BlazorWebAppAuthorization/Components/Pages/PassMinimumAge21PolicyWithAttribute.razor
new file mode 100644
index 00000000..fe5934d9
--- /dev/null
+++ b/security/authorization/BlazorWebAppAuthorization/Components/Pages/PassMinimumAge21PolicyWithAttribute.razor
@@ -0,0 +1,7 @@
+@page "/pass-minimumage21-policy-with-attribute"
+@using BlazorWebAppAuthorization.Policies.Attributes
+@attribute [MinimumAgeAuthorize(21)]
+
+Pass 'MinimumAge21' policy
+
+You satisfy the 'MinimumAge21' policy.
diff --git a/security/authorization/BlazorWebAppAuthorization/Policies/Attributes/MinimumAgeAuthorizeAttribute.cs b/security/authorization/BlazorWebAppAuthorization/Policies/Attributes/MinimumAgeAuthorizeAttribute.cs
new file mode 100644
index 00000000..f0b0fb23
--- /dev/null
+++ b/security/authorization/BlazorWebAppAuthorization/Policies/Attributes/MinimumAgeAuthorizeAttribute.cs
@@ -0,0 +1,27 @@
+using Microsoft.AspNetCore.Authorization;
+
+namespace BlazorWebAppAuthorization.Policies.Attributes;
+
+internal class MinimumAgeAuthorizeAttribute : AuthorizeAttribute
+{
+ const string POLICY_PREFIX = "MinimumAge";
+
+ public MinimumAgeAuthorizeAttribute(int age) => Age = age;
+
+ public int Age
+ {
+ get
+ {
+ if (int.TryParse(Policy.AsSpan(POLICY_PREFIX.Length), out var age))
+ {
+ return age;
+ }
+
+ return default;
+ }
+ set
+ {
+ Policy = $"{POLICY_PREFIX}{value}";
+ }
+ }
+}
diff --git a/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs b/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs
new file mode 100644
index 00000000..13830977
--- /dev/null
+++ b/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs
@@ -0,0 +1,34 @@
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Identity;
+using BlazorWebAppAuthorization.Policies.Requirements;
+
+namespace BlazorWebAppAuthorization.Policies.Providers;
+
+internal class MinimumAgePolicyProvider : IAuthorizationPolicyProvider
+{
+ const string POLICY_PREFIX = "MinimumAge";
+
+ public Task GetPolicyAsync(string policyName)
+ {
+ if (policyName.StartsWith(POLICY_PREFIX, StringComparison.OrdinalIgnoreCase) &&
+ int.TryParse(policyName.AsSpan(POLICY_PREFIX.Length), out var age))
+ {
+ var policy = new AuthorizationPolicyBuilder(
+ IdentityConstants.ApplicationScheme);
+ policy.AddRequirements(new MinimumAgeRequirement(age));
+
+ return Task.FromResult(policy.Build());
+ }
+
+ return Task.FromResult(null);
+ }
+
+ public Task GetDefaultPolicyAsync() =>
+ Task.FromResult(
+ new AuthorizationPolicyBuilder(
+ IdentityConstants.ApplicationScheme)
+ .RequireAuthenticatedUser().Build());
+
+ public Task GetFallbackPolicyAsync() =>
+ Task.FromResult(null);
+}
diff --git a/security/authorization/BlazorWebAppAuthorization/Program.cs b/security/authorization/BlazorWebAppAuthorization/Program.cs
index 6873e065..278e1bf5 100644
--- a/security/authorization/BlazorWebAppAuthorization/Program.cs
+++ b/security/authorization/BlazorWebAppAuthorization/Program.cs
@@ -7,6 +7,7 @@
using BlazorWebAppAuthorization.Data;
using BlazorWebAppAuthorization.Identity;
using BlazorWebAppAuthorization.Policies.Handlers;
+using BlazorWebAppAuthorization.Policies.Providers;
using BlazorWebAppAuthorization.Policies.Requirements;
var builder = WebApplication.CreateBuilder(args);
@@ -84,6 +85,7 @@
builder.Services.AddSingleton();
builder.Services.AddSingleton();
builder.Services.AddScoped();
+builder.Services.AddSingleton();
var app = builder.Build();
diff --git a/security/authorization/BlazorWebAppAuthorization/README.md b/security/authorization/BlazorWebAppAuthorization/README.md
index 5410de4b..03fbb7bf 100644
--- a/security/authorization/BlazorWebAppAuthorization/README.md
+++ b/security/authorization/BlazorWebAppAuthorization/README.md
@@ -33,17 +33,17 @@ For more information, see the following resources:
* A `Department` claim with a `Human Resources` value.
* The author of a test resource with an `Id` (Guid) of `aaaabbbb-0000-cccc-1111-dddd2222eeee` (Test Document 1).
* Can perform full CRUD operations on resources in the `AccessDocumentCrud` page.
- * Satisfies the 'AtLeast21' policy with a `DateOfBirth` claim with a value of `2000-01-01`.
+ * Satisfies the 'AtLeast21' and 'MinimumAge21' policies with a `DateOfBirth` claim with a value of `2000-01-01`.
* `harry@contoso.com` (Password: `Passw0rd!`)
* The `Admin` role.
* An `EmployeeNumber` claim with a value of `10`.
* A `Department` claim with a `Customer Service` value.
* The author of a test resource with an `Id` (Guid) of `00001111-aaaa-2222-bbbb-3333cccc4444` (Test Document 2).
* Can create, read, and update resources in the `AccessDocumentCrud` page.
- * Doesn't satisfy the 'AtLeast21' policy with a `DateOfBirth` claim with a value of `2008-01-01`.
+ * Doesn't satisfy the 'AtLeast21' and 'MinimumAge21' policies with a `DateOfBirth` claim with a value of `2008-01-01`.
* `sarah@contoso.com` (Password: `Passw0rd!`)
* The `SuperUser` role.
* Doesn't have claims as an employee (`EmployeeNumber`) or for a department (`Department`).
* The author of a test resource with an `Id` (Guid) of `11112222-bbbb-3333-cccc-4444dddd5555` (Test Document 3).
* Can delete and read resources in the `AccessDocumentCrud` page.
- * Doesn't satisfy the 'AtLeast21' policy with a `DateOfBirth` claim with a value of `2008-01-01`.
+ * Doesn't satisfy the 'AtLeast21' and 'MinimumAge21' policies with a `DateOfBirth` claim with a value of `2008-01-01`.
From 642f0febaab7fe35d20caaa803a654cdf8b42436 Mon Sep 17 00:00:00 2001
From: Luke Latham <1622880+guardrex@users.noreply.github.com>
Date: Fri, 24 Jul 2026 13:11:09 -0400
Subject: [PATCH 02/17] Apply suggestions from code review
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---
.../Policies/Attributes/MinimumAgeAuthorizeAttribute.cs | 4 +++-
.../Policies/Providers/MinimumAgePolicyProvider.cs | 7 ++++++-
2 files changed, 9 insertions(+), 2 deletions(-)
diff --git a/security/authorization/BlazorWebAppAuthorization/Policies/Attributes/MinimumAgeAuthorizeAttribute.cs b/security/authorization/BlazorWebAppAuthorization/Policies/Attributes/MinimumAgeAuthorizeAttribute.cs
index f0b0fb23..19921785 100644
--- a/security/authorization/BlazorWebAppAuthorization/Policies/Attributes/MinimumAgeAuthorizeAttribute.cs
+++ b/security/authorization/BlazorWebAppAuthorization/Policies/Attributes/MinimumAgeAuthorizeAttribute.cs
@@ -12,7 +12,9 @@ public int Age
{
get
{
- if (int.TryParse(Policy.AsSpan(POLICY_PREFIX.Length), out var age))
+ if (!string.IsNullOrEmpty(Policy) &&
+ Policy.StartsWith(POLICY_PREFIX, System.StringComparison.OrdinalIgnoreCase) &&
+ int.TryParse(Policy.AsSpan(POLICY_PREFIX.Length), out var age))
{
return age;
}
diff --git a/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs b/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs
index 13830977..6398f804 100644
--- a/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs
+++ b/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs
@@ -6,12 +6,17 @@ namespace BlazorWebAppAuthorization.Policies.Providers;
internal class MinimumAgePolicyProvider : IAuthorizationPolicyProvider
{
+ private readonly DefaultAuthorizationPolicyProvider _fallbackPolicyProvider;
const string POLICY_PREFIX = "MinimumAge";
+ public MinimumAgePolicyProvider(Microsoft.Extensions.Options.IOptions options) =>
+ _fallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
+
public Task GetPolicyAsync(string policyName)
{
if (policyName.StartsWith(POLICY_PREFIX, StringComparison.OrdinalIgnoreCase) &&
- int.TryParse(policyName.AsSpan(POLICY_PREFIX.Length), out var age))
+ int.TryParse(policyName.AsSpan(POLICY_PREFIX.Length), out var age) &&
+ age >= 0)
{
var policy = new AuthorizationPolicyBuilder(
IdentityConstants.ApplicationScheme);
From 2820e534c6d0114d5492aff2d8c1d747671ac747 Mon Sep 17 00:00:00 2001
From: guardrex <1622880+guardrex@users.noreply.github.com>
Date: Fri, 24 Jul 2026 13:12:30 -0400
Subject: [PATCH 03/17] Updates
---
.../Policies/Attributes/MinimumAgeAuthorizeAttribute.cs | 3 ++-
.../Policies/Providers/MinimumAgePolicyProvider.cs | 3 ++-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/security/authorization/BlazorWebAppAuthorization/Policies/Attributes/MinimumAgeAuthorizeAttribute.cs b/security/authorization/BlazorWebAppAuthorization/Policies/Attributes/MinimumAgeAuthorizeAttribute.cs
index 19921785..211212a0 100644
--- a/security/authorization/BlazorWebAppAuthorization/Policies/Attributes/MinimumAgeAuthorizeAttribute.cs
+++ b/security/authorization/BlazorWebAppAuthorization/Policies/Attributes/MinimumAgeAuthorizeAttribute.cs
@@ -13,7 +13,8 @@ public int Age
get
{
if (!string.IsNullOrEmpty(Policy) &&
- Policy.StartsWith(POLICY_PREFIX, System.StringComparison.OrdinalIgnoreCase) &&
+ Policy.StartsWith(POLICY_PREFIX,
+ System.StringComparison.OrdinalIgnoreCase) &&
int.TryParse(Policy.AsSpan(POLICY_PREFIX.Length), out var age))
{
return age;
diff --git a/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs b/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs
index 6398f804..8fc03326 100644
--- a/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs
+++ b/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs
@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
+using Microsoft.Extensions.Options;
using BlazorWebAppAuthorization.Policies.Requirements;
namespace BlazorWebAppAuthorization.Policies.Providers;
@@ -9,7 +10,7 @@ internal class MinimumAgePolicyProvider : IAuthorizationPolicyProvider
private readonly DefaultAuthorizationPolicyProvider _fallbackPolicyProvider;
const string POLICY_PREFIX = "MinimumAge";
- public MinimumAgePolicyProvider(Microsoft.Extensions.Options.IOptions options) =>
+ public MinimumAgePolicyProvider(IOptions options) =>
_fallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
public Task GetPolicyAsync(string policyName)
From 45d38d4f6aae0a367bc432df47708c4417990533 Mon Sep 17 00:00:00 2001
From: guardrex <1622880+guardrex@users.noreply.github.com>
Date: Mon, 27 Jul 2026 08:30:31 -0400
Subject: [PATCH 04/17] Updates
---
.../Components/Pages/PassMinimumAge21Policy.razor | 8 +++++++-
.../PassMinimumAge21PolicyWithAttribute.razor | 8 +++++++-
.../Policies/Providers/MinimumAgePolicyProvider.cs | 14 +++++++-------
3 files changed, 21 insertions(+), 9 deletions(-)
diff --git a/security/authorization/BlazorWebAppAuthorization/Components/Pages/PassMinimumAge21Policy.razor b/security/authorization/BlazorWebAppAuthorization/Components/Pages/PassMinimumAge21Policy.razor
index 96472fd4..1563daa8 100644
--- a/security/authorization/BlazorWebAppAuthorization/Components/Pages/PassMinimumAge21Policy.razor
+++ b/security/authorization/BlazorWebAppAuthorization/Components/Pages/PassMinimumAge21Policy.razor
@@ -1,6 +1,12 @@
@page "/pass-minimumage21-policy"
-Pass 'MinimumAge21' policy with AuthorizeView
+Pass 'MinimumAge21' policy (weakly-typed approach)
+
+
+ Uses an AuthorizeView component to apply the policy using the policy's name.
+ This approach is shown for demonstration purposes, and isn't generally
+ recommended for production code.
+
diff --git a/security/authorization/BlazorWebAppAuthorization/Components/Pages/PassMinimumAge21PolicyWithAttribute.razor b/security/authorization/BlazorWebAppAuthorization/Components/Pages/PassMinimumAge21PolicyWithAttribute.razor
index fe5934d9..0effe4a2 100644
--- a/security/authorization/BlazorWebAppAuthorization/Components/Pages/PassMinimumAge21PolicyWithAttribute.razor
+++ b/security/authorization/BlazorWebAppAuthorization/Components/Pages/PassMinimumAge21PolicyWithAttribute.razor
@@ -2,6 +2,12 @@
@using BlazorWebAppAuthorization.Policies.Attributes
@attribute [MinimumAgeAuthorize(21)]
-Pass 'MinimumAge21' policy
+Pass 'MinimumAge21' policy (strongly-typed approach)
+
+
+ Applies the policy to the Razor component with an [Authorize] attribute.
+ This approach is preferred for production code, as it's strongly-typed
+ and avoids the use of a string to set the policy and minimum age.
+
You satisfy the 'MinimumAge21' policy.
diff --git a/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs b/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs
index 8fc03326..60daf288 100644
--- a/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs
+++ b/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs
@@ -5,17 +5,17 @@
namespace BlazorWebAppAuthorization.Policies.Providers;
-internal class MinimumAgePolicyProvider : IAuthorizationPolicyProvider
+internal class MinimumAgePolicyProvider(IOptions options)
+ : IAuthorizationPolicyProvider
{
- private readonly DefaultAuthorizationPolicyProvider _fallbackPolicyProvider;
+ private readonly DefaultAuthorizationPolicyProvider fallbackPolicyProvider =
+ new(options);
const string POLICY_PREFIX = "MinimumAge";
- public MinimumAgePolicyProvider(IOptions options) =>
- _fallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
-
public Task GetPolicyAsync(string policyName)
{
- if (policyName.StartsWith(POLICY_PREFIX, StringComparison.OrdinalIgnoreCase) &&
+ if (policyName.StartsWith(
+ POLICY_PREFIX, StringComparison.OrdinalIgnoreCase) &&
int.TryParse(policyName.AsSpan(POLICY_PREFIX.Length), out var age) &&
age >= 0)
{
@@ -30,7 +30,7 @@ public MinimumAgePolicyProvider(IOptions options) =>
}
public Task GetDefaultPolicyAsync() =>
- Task.FromResult(
+ Task.FromResult(
new AuthorizationPolicyBuilder(
IdentityConstants.ApplicationScheme)
.RequireAuthenticatedUser().Build());
From fd52a1242320e1c21d2b40aff0a323e21b129d87 Mon Sep 17 00:00:00 2001
From: guardrex <1622880+guardrex@users.noreply.github.com>
Date: Mon, 27 Jul 2026 08:37:56 -0400
Subject: [PATCH 05/17] Updates
---
.../Components/Pages/PassMinimumAge21Policy.razor | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/security/authorization/BlazorWebAppAuthorization/Components/Pages/PassMinimumAge21Policy.razor b/security/authorization/BlazorWebAppAuthorization/Components/Pages/PassMinimumAge21Policy.razor
index 1563daa8..20cf4e1f 100644
--- a/security/authorization/BlazorWebAppAuthorization/Components/Pages/PassMinimumAge21Policy.razor
+++ b/security/authorization/BlazorWebAppAuthorization/Components/Pages/PassMinimumAge21Policy.razor
@@ -4,8 +4,8 @@
Uses an AuthorizeView component to apply the policy using the policy's name.
- This approach is shown for demonstration purposes, and isn't generally
- recommended for production code.
+ This approach is shown for demonstration purposes, and isn't recommended for
+ production code.
From f4d3cd16d80ec02ca8ef0a561d1451ab8321b998 Mon Sep 17 00:00:00 2001
From: guardrex <1622880+guardrex@users.noreply.github.com>
Date: Mon, 27 Jul 2026 09:33:23 -0400
Subject: [PATCH 06/17] Updates
---
.../Policies/Providers/MinimumAgePolicyProvider.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs b/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs
index 60daf288..433f97eb 100644
--- a/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs
+++ b/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs
@@ -5,10 +5,10 @@
namespace BlazorWebAppAuthorization.Policies.Providers;
-internal class MinimumAgePolicyProvider(IOptions options)
+internal class MinimumAgePolicyProvider(IOptions options)
: IAuthorizationPolicyProvider
{
- private readonly DefaultAuthorizationPolicyProvider fallbackPolicyProvider =
+ private readonly DefaultAuthorizationPolicyProvider fallbackPolicyProvider =
new(options);
const string POLICY_PREFIX = "MinimumAge";
From 741666b1db67d3ec7a8be81808b7a87d4d5197d7 Mon Sep 17 00:00:00 2001
From: guardrex <1622880+guardrex@users.noreply.github.com>
Date: Mon, 27 Jul 2026 11:38:08 -0400
Subject: [PATCH 07/17] Updates
---
.../Pages/PassMinimumAge21Policy.razor | 2 +-
.../Providers/MinimumAgePolicyProvider.cs | 19 ++++++++-----------
2 files changed, 9 insertions(+), 12 deletions(-)
diff --git a/security/authorization/BlazorWebAppAuthorization/Components/Pages/PassMinimumAge21Policy.razor b/security/authorization/BlazorWebAppAuthorization/Components/Pages/PassMinimumAge21Policy.razor
index 20cf4e1f..8f39f26b 100644
--- a/security/authorization/BlazorWebAppAuthorization/Components/Pages/PassMinimumAge21Policy.razor
+++ b/security/authorization/BlazorWebAppAuthorization/Components/Pages/PassMinimumAge21Policy.razor
@@ -4,7 +4,7 @@
Uses an AuthorizeView component to apply the policy using the policy's name.
- This approach is shown for demonstration purposes, and isn't recommended for
+ This approach is shown for demonstration purposes and isn't recommended for
production code.
diff --git a/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs b/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs
index 433f97eb..34948f50 100644
--- a/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs
+++ b/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs
@@ -1,16 +1,16 @@
+using BlazorWebAppAuthorization.Policies.Requirements;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
-using BlazorWebAppAuthorization.Policies.Requirements;
namespace BlazorWebAppAuthorization.Policies.Providers;
internal class MinimumAgePolicyProvider(IOptions options)
: IAuthorizationPolicyProvider
{
- private readonly DefaultAuthorizationPolicyProvider fallbackPolicyProvider =
- new(options);
const string POLICY_PREFIX = "MinimumAge";
+ public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; } =
+ new(options);
public Task GetPolicyAsync(string policyName)
{
@@ -26,15 +26,12 @@ internal class MinimumAgePolicyProvider(IOptions options)
return Task.FromResult(policy.Build());
}
- return Task.FromResult(null);
+ return FallbackPolicyProvider.GetPolicyAsync(policyName);
}
- public Task GetDefaultPolicyAsync() =>
- Task.FromResult(
- new AuthorizationPolicyBuilder(
- IdentityConstants.ApplicationScheme)
- .RequireAuthenticatedUser().Build());
+ public Task GetDefaultPolicyAsync() =>
+ FallbackPolicyProvider.GetDefaultPolicyAsync();
- public Task GetFallbackPolicyAsync() =>
- Task.FromResult(null);
+ public Task GetFallbackPolicyAsync() =>
+ FallbackPolicyProvider.GetFallbackPolicyAsync();
}
From 43ea78d16d93fd66a75e7c854196e45baea3f162 Mon Sep 17 00:00:00 2001
From: guardrex <1622880+guardrex@users.noreply.github.com>
Date: Mon, 27 Jul 2026 12:01:30 -0400
Subject: [PATCH 08/17] Updates
---
.../Attributes/MinimumAgeAuthorizeAttribute.cs | 11 ++++++-----
.../Policies/Providers/MinimumAgePolicyProvider.cs | 9 +++++----
2 files changed, 11 insertions(+), 9 deletions(-)
diff --git a/security/authorization/BlazorWebAppAuthorization/Policies/Attributes/MinimumAgeAuthorizeAttribute.cs b/security/authorization/BlazorWebAppAuthorization/Policies/Attributes/MinimumAgeAuthorizeAttribute.cs
index 211212a0..5132ac8a 100644
--- a/security/authorization/BlazorWebAppAuthorization/Policies/Attributes/MinimumAgeAuthorizeAttribute.cs
+++ b/security/authorization/BlazorWebAppAuthorization/Policies/Attributes/MinimumAgeAuthorizeAttribute.cs
@@ -4,7 +4,7 @@ namespace BlazorWebAppAuthorization.Policies.Attributes;
internal class MinimumAgeAuthorizeAttribute : AuthorizeAttribute
{
- const string POLICY_PREFIX = "MinimumAge";
+ private const string PolicyPrefix = "MinimumAge";
public MinimumAgeAuthorizeAttribute(int age) => Age = age;
@@ -13,9 +13,9 @@ public int Age
get
{
if (!string.IsNullOrEmpty(Policy) &&
- Policy.StartsWith(POLICY_PREFIX,
- System.StringComparison.OrdinalIgnoreCase) &&
- int.TryParse(Policy.AsSpan(POLICY_PREFIX.Length), out var age))
+ Policy.StartsWith(PolicyPrefix,
+ StringComparison.OrdinalIgnoreCase) &&
+ int.TryParse(Policy.AsSpan(PolicyPrefix.Length), out var age))
{
return age;
}
@@ -24,7 +24,8 @@ public int Age
}
set
{
- Policy = $"{POLICY_PREFIX}{value}";
+ ArgumentOutOfRangeException.ThrowIfNegative(value);
+ Policy = $"{PolicyPrefix}{value}";
}
}
}
diff --git a/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs b/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs
index 34948f50..06e879d9 100644
--- a/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs
+++ b/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs
@@ -8,15 +8,16 @@ namespace BlazorWebAppAuthorization.Policies.Providers;
internal class MinimumAgePolicyProvider(IOptions options)
: IAuthorizationPolicyProvider
{
- const string POLICY_PREFIX = "MinimumAge";
- public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; } =
+ private const string PolicyPrefix = "MinimumAge";
+
+ private DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; } =
new(options);
public Task GetPolicyAsync(string policyName)
{
if (policyName.StartsWith(
- POLICY_PREFIX, StringComparison.OrdinalIgnoreCase) &&
- int.TryParse(policyName.AsSpan(POLICY_PREFIX.Length), out var age) &&
+ PolicyPrefix, StringComparison.OrdinalIgnoreCase) &&
+ int.TryParse(policyName.AsSpan(PolicyPrefix.Length), out var age) &&
age >= 0)
{
var policy = new AuthorizationPolicyBuilder(
From 759413099f8bd1424bccd96b8108f61435c6cc2d Mon Sep 17 00:00:00 2001
From: guardrex <1622880+guardrex@users.noreply.github.com>
Date: Mon, 27 Jul 2026 12:12:10 -0400
Subject: [PATCH 09/17] Updates
---
.../Components/Pages/PassMinimumAge21PolicyWithAttribute.razor | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/security/authorization/BlazorWebAppAuthorization/Components/Pages/PassMinimumAge21PolicyWithAttribute.razor b/security/authorization/BlazorWebAppAuthorization/Components/Pages/PassMinimumAge21PolicyWithAttribute.razor
index 0effe4a2..5999afda 100644
--- a/security/authorization/BlazorWebAppAuthorization/Components/Pages/PassMinimumAge21PolicyWithAttribute.razor
+++ b/security/authorization/BlazorWebAppAuthorization/Components/Pages/PassMinimumAge21PolicyWithAttribute.razor
@@ -5,7 +5,8 @@
Pass 'MinimumAge21' policy (strongly-typed approach)
- Applies the policy to the Razor component with an [Authorize] attribute.
+ Applies the policy to the Razor component with a custom
+ [MinimumAgeAuthorize] attribute (derived from AuthorizeAttribute).
This approach is preferred for production code, as it's strongly-typed
and avoids the use of a string to set the policy and minimum age.
From 693a7942acb2d8e3d857c1773005ab72bce43f94 Mon Sep 17 00:00:00 2001
From: Luke Latham <1622880+guardrex@users.noreply.github.com>
Date: Mon, 27 Jul 2026 12:40:21 -0400
Subject: [PATCH 10/17] Apply suggestions from code review
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---
.../Policies/Attributes/MinimumAgeAuthorizeAttribute.cs | 2 +-
.../Policies/Providers/MinimumAgePolicyProvider.cs | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/security/authorization/BlazorWebAppAuthorization/Policies/Attributes/MinimumAgeAuthorizeAttribute.cs b/security/authorization/BlazorWebAppAuthorization/Policies/Attributes/MinimumAgeAuthorizeAttribute.cs
index 5132ac8a..3ae273d2 100644
--- a/security/authorization/BlazorWebAppAuthorization/Policies/Attributes/MinimumAgeAuthorizeAttribute.cs
+++ b/security/authorization/BlazorWebAppAuthorization/Policies/Attributes/MinimumAgeAuthorizeAttribute.cs
@@ -2,7 +2,7 @@
namespace BlazorWebAppAuthorization.Policies.Attributes;
-internal class MinimumAgeAuthorizeAttribute : AuthorizeAttribute
+public class MinimumAgeAuthorizeAttribute : AuthorizeAttribute
{
private const string PolicyPrefix = "MinimumAge";
diff --git a/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs b/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs
index 06e879d9..7b5d6bce 100644
--- a/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs
+++ b/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs
@@ -5,7 +5,7 @@
namespace BlazorWebAppAuthorization.Policies.Providers;
-internal class MinimumAgePolicyProvider(IOptions options)
+public class MinimumAgePolicyProvider(IOptions options)
: IAuthorizationPolicyProvider
{
private const string PolicyPrefix = "MinimumAge";
From ae185a1894f77fa04d46b7d6a45d48408d858399 Mon Sep 17 00:00:00 2001
From: Luke Latham <1622880+guardrex@users.noreply.github.com>
Date: Wed, 29 Jul 2026 11:08:04 -0400
Subject: [PATCH 11/17] Clarify policy satisfaction language in README
---
security/authorization/BlazorWebAppAuthorization/README.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/security/authorization/BlazorWebAppAuthorization/README.md b/security/authorization/BlazorWebAppAuthorization/README.md
index 03fbb7bf..8e6f7160 100644
--- a/security/authorization/BlazorWebAppAuthorization/README.md
+++ b/security/authorization/BlazorWebAppAuthorization/README.md
@@ -33,17 +33,17 @@ For more information, see the following resources:
* A `Department` claim with a `Human Resources` value.
* The author of a test resource with an `Id` (Guid) of `aaaabbbb-0000-cccc-1111-dddd2222eeee` (Test Document 1).
* Can perform full CRUD operations on resources in the `AccessDocumentCrud` page.
- * Satisfies the 'AtLeast21' and 'MinimumAge21' policies with a `DateOfBirth` claim with a value of `2000-01-01`.
+ * Satisfies the 'AtLeast21' and 'MinimumAge21' policies, having a `DateOfBirth` claim with a value of `2000-01-01`.
* `harry@contoso.com` (Password: `Passw0rd!`)
* The `Admin` role.
* An `EmployeeNumber` claim with a value of `10`.
* A `Department` claim with a `Customer Service` value.
* The author of a test resource with an `Id` (Guid) of `00001111-aaaa-2222-bbbb-3333cccc4444` (Test Document 2).
* Can create, read, and update resources in the `AccessDocumentCrud` page.
- * Doesn't satisfy the 'AtLeast21' and 'MinimumAge21' policies with a `DateOfBirth` claim with a value of `2008-01-01`.
+ * Doesn't satisfy the 'AtLeast21' and 'MinimumAge21' policies, having a `DateOfBirth` claim with a value of `2008-01-01`.
* `sarah@contoso.com` (Password: `Passw0rd!`)
* The `SuperUser` role.
* Doesn't have claims as an employee (`EmployeeNumber`) or for a department (`Department`).
* The author of a test resource with an `Id` (Guid) of `11112222-bbbb-3333-cccc-4444dddd5555` (Test Document 3).
* Can delete and read resources in the `AccessDocumentCrud` page.
- * Doesn't satisfy the 'AtLeast21' and 'MinimumAge21' policies with a `DateOfBirth` claim with a value of `2008-01-01`.
+ * Doesn't satisfy the 'AtLeast21' and 'MinimumAge21' policies, having a `DateOfBirth` claim with a value of `2008-01-01`.
From f4140dac5cc3a234a3c942cc83b0b9b877678954 Mon Sep 17 00:00:00 2001
From: Luke Latham <1622880+guardrex@users.noreply.github.com>
Date: Wed, 29 Jul 2026 11:16:10 -0400
Subject: [PATCH 12/17] Clarify DateOfBirth claim descriptions in README
---
security/authorization/BlazorWebAppAuthorization/README.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/security/authorization/BlazorWebAppAuthorization/README.md b/security/authorization/BlazorWebAppAuthorization/README.md
index 8e6f7160..4db8cf6a 100644
--- a/security/authorization/BlazorWebAppAuthorization/README.md
+++ b/security/authorization/BlazorWebAppAuthorization/README.md
@@ -33,17 +33,17 @@ For more information, see the following resources:
* A `Department` claim with a `Human Resources` value.
* The author of a test resource with an `Id` (Guid) of `aaaabbbb-0000-cccc-1111-dddd2222eeee` (Test Document 1).
* Can perform full CRUD operations on resources in the `AccessDocumentCrud` page.
- * Satisfies the 'AtLeast21' and 'MinimumAge21' policies, having a `DateOfBirth` claim with a value of `2000-01-01`.
+ * Has a `DateOfBirth` claim with a value of `2000-01-01`, which satisfies the 'AtLeast21' and 'MinimumAge21' policies.
* `harry@contoso.com` (Password: `Passw0rd!`)
* The `Admin` role.
* An `EmployeeNumber` claim with a value of `10`.
* A `Department` claim with a `Customer Service` value.
* The author of a test resource with an `Id` (Guid) of `00001111-aaaa-2222-bbbb-3333cccc4444` (Test Document 2).
* Can create, read, and update resources in the `AccessDocumentCrud` page.
- * Doesn't satisfy the 'AtLeast21' and 'MinimumAge21' policies, having a `DateOfBirth` claim with a value of `2008-01-01`.
+ * Has a `DateOfBirth` claim with a value of `2008-01-01`, which doesn't satisfy the 'AtLeast21' and 'MinimumAge21' policies.
* `sarah@contoso.com` (Password: `Passw0rd!`)
* The `SuperUser` role.
* Doesn't have claims as an employee (`EmployeeNumber`) or for a department (`Department`).
* The author of a test resource with an `Id` (Guid) of `11112222-bbbb-3333-cccc-4444dddd5555` (Test Document 3).
* Can delete and read resources in the `AccessDocumentCrud` page.
- * Doesn't satisfy the 'AtLeast21' and 'MinimumAge21' policies, having a `DateOfBirth` claim with a value of `2008-01-01`.
+ * Has a `DateOfBirth` claim with a value of `2008-01-01`, which doesn't satisfy the 'AtLeast21' and 'MinimumAge21' policies.
From 7d662eeee0ca3d756bfe603da814875ecd4831ba Mon Sep 17 00:00:00 2001
From: guardrex <1622880+guardrex@users.noreply.github.com>
Date: Wed, 29 Jul 2026 11:18:26 -0400
Subject: [PATCH 13/17] Updates
---
.../Policies/Providers/MinimumAgePolicyProvider.cs | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs b/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs
index 7b5d6bce..fb225a70 100644
--- a/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs
+++ b/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs
@@ -10,7 +10,7 @@ public class MinimumAgePolicyProvider(IOptions options)
{
private const string PolicyPrefix = "MinimumAge";
- private DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; } =
+ private DefaultAuthorizationPolicyProvider DefaultPolicyProvider { get; } =
new(options);
public Task GetPolicyAsync(string policyName)
@@ -27,12 +27,12 @@ public class MinimumAgePolicyProvider(IOptions options)
return Task.FromResult(policy.Build());
}
- return FallbackPolicyProvider.GetPolicyAsync(policyName);
+ return DefaultPolicyProvider.GetPolicyAsync(policyName);
}
public Task GetDefaultPolicyAsync() =>
- FallbackPolicyProvider.GetDefaultPolicyAsync();
+ DefaultPolicyProvider.GetDefaultPolicyAsync();
public Task GetFallbackPolicyAsync() =>
- FallbackPolicyProvider.GetFallbackPolicyAsync();
+ DefaultPolicyProvider.GetFallbackPolicyAsync();
}
From 5a259d5ca42478fa45f027e3135fe8243c561de7 Mon Sep 17 00:00:00 2001
From: guardrex <1622880+guardrex@users.noreply.github.com>
Date: Wed, 29 Jul 2026 11:41:42 -0400
Subject: [PATCH 14/17] Updates
---
.../Components/Pages/Home.razor | 18 +++++++++++++-----
1 file changed, 13 insertions(+), 5 deletions(-)
diff --git a/security/authorization/BlazorWebAppAuthorization/Components/Pages/Home.razor b/security/authorization/BlazorWebAppAuthorization/Components/Pages/Home.razor
index bbbc59a3..829dc19e 100644
--- a/security/authorization/BlazorWebAppAuthorization/Components/Pages/Home.razor
+++ b/security/authorization/BlazorWebAppAuthorization/Components/Pages/Home.razor
@@ -80,11 +80,19 @@
Pass 'MinimumAge21' Policy
-
-
- Pass 'MinimumAge21' Policy (attribute-based)
-
-
+
+
+
+
+ Pass 'MinimumAge21' Policy (attribute-based)
+
+
+
+
+ You can't see this link to the 'PassMinimumAge21PolicyWithAttribute' component because you don't satisfy the 'MinimumAge21' policy.
+ If you navigate to the page at '/pass-minimumage21-policy-with-attribute' in the browser's address bar, the redirect for unauthorized access loads the app's sign-in page, where you can sign into the app with Leela's credentials to reach the page.
+
+
Sign into the app with one of the seeded accounts. See the README file for accounts and credentials.
From 0bedccf80984541a271554c88cff688055c235a6 Mon Sep 17 00:00:00 2001
From: guardrex <1622880+guardrex@users.noreply.github.com>
Date: Wed, 29 Jul 2026 11:45:27 -0400
Subject: [PATCH 15/17] Updates
---
.../BlazorWebAppAuthorization/Components/Pages/Home.razor | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/security/authorization/BlazorWebAppAuthorization/Components/Pages/Home.razor b/security/authorization/BlazorWebAppAuthorization/Components/Pages/Home.razor
index 829dc19e..df059c30 100644
--- a/security/authorization/BlazorWebAppAuthorization/Components/Pages/Home.razor
+++ b/security/authorization/BlazorWebAppAuthorization/Components/Pages/Home.razor
@@ -89,8 +89,11 @@
- You can't see this link to the 'PassMinimumAge21PolicyWithAttribute' component because you don't satisfy the 'MinimumAge21' policy.
- If you navigate to the page at '/pass-minimumage21-policy-with-attribute' in the browser's address bar, the redirect for unauthorized access loads the app's sign-in page, where you can sign into the app with Leela's credentials to reach the page.
+
+ You can't see this link to the 'PassMinimumAge21PolicyWithAttribute' component because you don't satisfy
+ the 'MinimumAge21' policy. If you navigate to the page at '/pass-minimumage21-policy-with-attribute' in
+ the browser's address bar, the Access Denied page is loaded.
+
From 7a5547dac7a941a142685a1ac4274ead3c9ea676 Mon Sep 17 00:00:00 2001
From: Luke Latham <1622880+guardrex@users.noreply.github.com>
Date: Wed, 29 Jul 2026 14:16:49 -0400
Subject: [PATCH 16/17] Update
security/authorization/BlazorWebAppAuthorization/Components/Pages/Home.razor
Co-authored-by: Wade Pickett
---
.../BlazorWebAppAuthorization/Components/Pages/Home.razor | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/security/authorization/BlazorWebAppAuthorization/Components/Pages/Home.razor b/security/authorization/BlazorWebAppAuthorization/Components/Pages/Home.razor
index df059c30..2fe18569 100644
--- a/security/authorization/BlazorWebAppAuthorization/Components/Pages/Home.razor
+++ b/security/authorization/BlazorWebAppAuthorization/Components/Pages/Home.razor
@@ -2,7 +2,7 @@
Home
-Roles Sample App
+Authorization Sample App
From bfb9cb0646569483901c81ff56088575ae90998c Mon Sep 17 00:00:00 2001
From: guardrex <1622880+guardrex@users.noreply.github.com>
Date: Wed, 29 Jul 2026 14:21:59 -0400
Subject: [PATCH 17/17] Update caps in link text in the Home component
---
.../BlazorWebAppAuthorization/Components/Pages/Home.razor | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/security/authorization/BlazorWebAppAuthorization/Components/Pages/Home.razor b/security/authorization/BlazorWebAppAuthorization/Components/Pages/Home.razor
index df059c30..c60f321c 100644
--- a/security/authorization/BlazorWebAppAuthorization/Components/Pages/Home.razor
+++ b/security/authorization/BlazorWebAppAuthorization/Components/Pages/Home.razor
@@ -9,13 +9,13 @@
Policy-based authorization example
- Pass 'AtLeast21' Policy
+ Pass 'AtLeast21' policy
Role-based authorization examples
- Role Checks with AuthorizeView
+ Role checks with AuthorizeView
@@ -77,14 +77,14 @@
Policy provider authorization example
- Pass 'MinimumAge21' Policy
+ Pass 'MinimumAge21' policy
- Pass 'MinimumAge21' Policy (attribute-based)
+ Pass 'MinimumAge21' policy (attribute-based)