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

+ +

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 - + + + + + +

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

Role-based authorization examples