Policy-based authorization example
- Pass 'AtLeast21' Policy
+ Pass 'AtLeast21' policy
Role-based authorization examples
- Role Checks with AuthorizeView
+ Role checks with AuthorizeView
@@ -74,6 +74,28 @@
Pass 'CustomerServiceMember' and 'HumanResourcesMember' policies with [Authorize] attributes
+ Policy provider authorization example
+
+
+ Pass 'MinimumAge21' policy
+
+
+
+
+
+
+ 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 Access Denied page is loaded.
+
+
+
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..8f39f26b
--- /dev/null
+++ b/security/authorization/BlazorWebAppAuthorization/Components/Pages/PassMinimumAge21Policy.razor
@@ -0,0 +1,18 @@
+@page "/pass-minimumage21-policy"
+
+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 recommended for
+ production code.
+
+
+
+
+ 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..5999afda
--- /dev/null
+++ b/security/authorization/BlazorWebAppAuthorization/Components/Pages/PassMinimumAge21PolicyWithAttribute.razor
@@ -0,0 +1,14 @@
+@page "/pass-minimumage21-policy-with-attribute"
+@using BlazorWebAppAuthorization.Policies.Attributes
+@attribute [MinimumAgeAuthorize(21)]
+
+Pass 'MinimumAge21' policy (strongly-typed approach)
+
+
+ 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.
+
+
+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..3ae273d2
--- /dev/null
+++ b/security/authorization/BlazorWebAppAuthorization/Policies/Attributes/MinimumAgeAuthorizeAttribute.cs
@@ -0,0 +1,31 @@
+using Microsoft.AspNetCore.Authorization;
+
+namespace BlazorWebAppAuthorization.Policies.Attributes;
+
+public class MinimumAgeAuthorizeAttribute : AuthorizeAttribute
+{
+ private const string PolicyPrefix = "MinimumAge";
+
+ public MinimumAgeAuthorizeAttribute(int age) => Age = age;
+
+ public int Age
+ {
+ get
+ {
+ if (!string.IsNullOrEmpty(Policy) &&
+ Policy.StartsWith(PolicyPrefix,
+ StringComparison.OrdinalIgnoreCase) &&
+ int.TryParse(Policy.AsSpan(PolicyPrefix.Length), out var age))
+ {
+ return age;
+ }
+
+ return default;
+ }
+ set
+ {
+ ArgumentOutOfRangeException.ThrowIfNegative(value);
+ Policy = $"{PolicyPrefix}{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..fb225a70
--- /dev/null
+++ b/security/authorization/BlazorWebAppAuthorization/Policies/Providers/MinimumAgePolicyProvider.cs
@@ -0,0 +1,38 @@
+using BlazorWebAppAuthorization.Policies.Requirements;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.Extensions.Options;
+
+namespace BlazorWebAppAuthorization.Policies.Providers;
+
+public class MinimumAgePolicyProvider(IOptions options)
+ : IAuthorizationPolicyProvider
+{
+ private const string PolicyPrefix = "MinimumAge";
+
+ private DefaultAuthorizationPolicyProvider DefaultPolicyProvider { get; } =
+ new(options);
+
+ public Task GetPolicyAsync(string policyName)
+ {
+ if (policyName.StartsWith(
+ PolicyPrefix, StringComparison.OrdinalIgnoreCase) &&
+ int.TryParse(policyName.AsSpan(PolicyPrefix.Length), out var age) &&
+ age >= 0)
+ {
+ var policy = new AuthorizationPolicyBuilder(
+ IdentityConstants.ApplicationScheme);
+ policy.AddRequirements(new MinimumAgeRequirement(age));
+
+ return Task.FromResult(policy.Build());
+ }
+
+ return DefaultPolicyProvider.GetPolicyAsync(policyName);
+ }
+
+ public Task GetDefaultPolicyAsync() =>
+ DefaultPolicyProvider.GetDefaultPolicyAsync();
+
+ public Task GetFallbackPolicyAsync() =>
+ DefaultPolicyProvider.GetFallbackPolicyAsync();
+}
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..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' policy with 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' policy with 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' policy with 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.