Add IAuthorizationPolicyProvider sample (#31)
* Add IAuthorizationPolicyProvider sample * Update IAuthorizationPolicyProvider sample to show falling back to a default provider * Make project, directory, and namespace names consistent for custom policy provider sample * Make policy provider sample an MVC app and replace Values controller with a home controller (with views) * Add cookie authentication and account controller/views * Update readme and comments to reflect new authorization policy provider scenario * Add some more information to the access denied page for debugging purposes * Add some additional logging * Minor updates based on PR feedback
This commit is contained in:
parent
2abf5de0e3
commit
8d0f84ea46
|
|
@ -9,6 +9,7 @@ packages/
|
|||
artifacts/
|
||||
PublishProfiles/
|
||||
.vs/
|
||||
.vscode/
|
||||
*.user
|
||||
*.suo
|
||||
*.cache
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Identity.ExternalClaims", "
|
|||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DynamicSchemes", "samples\DynamicSchemes\DynamicSchemes.csproj", "{F2F7A64C-870C-40C9-B5FC-F8952F1572B3}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomPolicyProvider", "samples\CustomPolicyProvider\CustomPolicyProvider.csproj", "{70299871-8FF5-4521-AD56-48BB6E07BA13}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StaticFilesAuth", "samples\StaticFilesAuth\StaticFilesAuth.csproj", "{0F013930-E66F-4F8B-95BE-CDFB417ACE3E}"
|
||||
EndProject
|
||||
Global
|
||||
|
|
@ -100,6 +102,18 @@ Global
|
|||
{F2F7A64C-870C-40C9-B5FC-F8952F1572B3}.Release|x64.Build.0 = Release|Any CPU
|
||||
{F2F7A64C-870C-40C9-B5FC-F8952F1572B3}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{F2F7A64C-870C-40C9-B5FC-F8952F1572B3}.Release|x86.Build.0 = Release|Any CPU
|
||||
{70299871-8FF5-4521-AD56-48BB6E07BA13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{70299871-8FF5-4521-AD56-48BB6E07BA13}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{70299871-8FF5-4521-AD56-48BB6E07BA13}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{70299871-8FF5-4521-AD56-48BB6E07BA13}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{70299871-8FF5-4521-AD56-48BB6E07BA13}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{70299871-8FF5-4521-AD56-48BB6E07BA13}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{70299871-8FF5-4521-AD56-48BB6E07BA13}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{70299871-8FF5-4521-AD56-48BB6E07BA13}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{70299871-8FF5-4521-AD56-48BB6E07BA13}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{70299871-8FF5-4521-AD56-48BB6E07BA13}.Release|x64.Build.0 = Release|Any CPU
|
||||
{70299871-8FF5-4521-AD56-48BB6E07BA13}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{70299871-8FF5-4521-AD56-48BB6E07BA13}.Release|x86.Build.0 = Release|Any CPU
|
||||
{0F013930-E66F-4F8B-95BE-CDFB417ACE3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0F013930-E66F-4F8B-95BE-CDFB417ACE3E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0F013930-E66F-4F8B-95BE-CDFB417ACE3E}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
|
|
@ -123,6 +137,7 @@ Global
|
|||
{4E91BD2A-616F-45EE-9647-2F1608D17FB9} = {CA4538F5-9DA8-4139-B891-A13279889F79}
|
||||
{D8804E7A-BD7A-4E4B-ACA7-822A37A81B28} = {CA4538F5-9DA8-4139-B891-A13279889F79}
|
||||
{F2F7A64C-870C-40C9-B5FC-F8952F1572B3} = {CA4538F5-9DA8-4139-B891-A13279889F79}
|
||||
{70299871-8FF5-4521-AD56-48BB6E07BA13} = {CA4538F5-9DA8-4139-B891-A13279889F79}
|
||||
{0F013930-E66F-4F8B-95BE-CDFB417ACE3E} = {CA4538F5-9DA8-4139-B891-A13279889F79}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
using System;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CustomPolicyProvider
|
||||
{
|
||||
// This class contains logic for determining whether MinimumAgeRequirements in authorizaiton
|
||||
// policies are satisfied or not
|
||||
internal class MinimumAgeAuthorizationHandler : AuthorizationHandler<MinimumAgeRequirement>
|
||||
{
|
||||
private readonly ILogger<MinimumAgeAuthorizationHandler> _logger;
|
||||
|
||||
public MinimumAgeAuthorizationHandler(ILogger<MinimumAgeAuthorizationHandler> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
// Check whether a given MinimumAgeRequirement is satisfied or not for a particular context
|
||||
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MinimumAgeRequirement requirement)
|
||||
{
|
||||
// Log as a warning so that it's very clear in sample output which authorization policies
|
||||
// (and requirements/handlers) are in use
|
||||
_logger.LogWarning("Evaluating authorization requirement for age >= {age}", requirement.Age);
|
||||
|
||||
// Check the user's age
|
||||
var dateOfBirthClaim = context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth);
|
||||
if (dateOfBirthClaim != null)
|
||||
{
|
||||
// If the user has a date of birth claim, check their age
|
||||
var dateOfBirth = Convert.ToDateTime(dateOfBirthClaim.Value);
|
||||
var age = DateTime.Now.Year - dateOfBirth.Year;
|
||||
if (dateOfBirth > DateTime.Now.AddYears(-age))
|
||||
{
|
||||
// Adjust age if the user hasn't had a birthday yet this year
|
||||
age--;
|
||||
}
|
||||
|
||||
// If the user meets the age criterion, mark the authorization requirement succeeded
|
||||
if (age >= requirement.Age)
|
||||
{
|
||||
_logger.LogInformation("Minimum age authorization requirement {age} satisfied", requirement.Age);
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("Current user's DateOfBirth claim ({dateOfBirth}) does not satisfy the minimum age authorization requirement {age}",
|
||||
dateOfBirthClaim.Value,
|
||||
requirement.Age);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("No DateOfBirth claim present");
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace CustomPolicyProvider
|
||||
{
|
||||
// This attribute derives from the [Authorize] attribute, adding
|
||||
// the ability for a user to specify an 'age' paratmer. Since authorization
|
||||
// policies are looked up from the policy provider only by string, this
|
||||
// authorization attribute creates is policy name based on a constant prefix
|
||||
// and the user-supplied age parameter. A custom authorization policy provider
|
||||
// (`MinimumAgePolicyProvider`) can then produce an authorization policy with
|
||||
// the necessary requirements based on this policy name.
|
||||
internal class MinimumAgeAuthorizeAttribute : AuthorizeAttribute
|
||||
{
|
||||
const string POLICY_PREFIX = "MinimumAge";
|
||||
|
||||
public MinimumAgeAuthorizeAttribute(int age) => Age = age;
|
||||
|
||||
// Get or set the Age property by manipulating the underlying Policy property
|
||||
public int Age
|
||||
{
|
||||
get
|
||||
{
|
||||
if (int.TryParse(Policy.Substring(POLICY_PREFIX.Length), out var age))
|
||||
{
|
||||
return age;
|
||||
}
|
||||
return default(int);
|
||||
}
|
||||
set
|
||||
{
|
||||
Policy = $"{POLICY_PREFIX}{value.ToString()}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace CustomPolicyProvider
|
||||
{
|
||||
internal class MinimumAgePolicyProvider : IAuthorizationPolicyProvider
|
||||
{
|
||||
const string POLICY_PREFIX = "MinimumAge";
|
||||
public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; }
|
||||
|
||||
public MinimumAgePolicyProvider(IOptions<AuthorizationOptions> options)
|
||||
{
|
||||
// ASP.NET Core only uses one authorization policy provider, so if the custom implementation
|
||||
// doesn't handle all policies (including default policies, etc.) it should fall back to an
|
||||
// alternate provider.
|
||||
//
|
||||
// In this sample, a default authorization policy provider (constructed with options from the
|
||||
// dependency injection container) is used if this custom provider isn't able to handle a given
|
||||
// policy name.
|
||||
//
|
||||
// If a custom policy provider is able to handle all expected policy names then, of course, this
|
||||
// fallback pattern is unnecessary.
|
||||
FallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
|
||||
}
|
||||
|
||||
public Task<AuthorizationPolicy> GetDefaultPolicyAsync() => FallbackPolicyProvider.GetDefaultPolicyAsync();
|
||||
|
||||
// Policies are looked up by string name, so expect 'parameters' (like age)
|
||||
// to be embedded in the policy names. This is abstracted away from developers
|
||||
// by the more strongly-typed attributes derived from AuthorizeAttribute
|
||||
// (like [MinimumAgeAuthorize] in this sample)
|
||||
public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
|
||||
{
|
||||
if (policyName.StartsWith(POLICY_PREFIX, StringComparison.OrdinalIgnoreCase) &&
|
||||
int.TryParse(policyName.Substring(POLICY_PREFIX.Length), out var age))
|
||||
{
|
||||
var policy = new AuthorizationPolicyBuilder();
|
||||
policy.AddRequirements(new MinimumAgeRequirement(age));
|
||||
return Task.FromResult(policy.Build());
|
||||
}
|
||||
|
||||
// If the policy name doesn't match the format expected by this policy provider,
|
||||
// try the fallback provider. If no fallback provider is used, this would return
|
||||
// Task.FromResult<AuthorizationPolicy>(null) instead.
|
||||
return FallbackPolicyProvider.GetPolicyAsync(policyName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace CustomPolicyProvider
|
||||
{
|
||||
internal class MinimumAgeRequirement : IAuthorizationRequirement
|
||||
{
|
||||
public int Age { get; private set; }
|
||||
|
||||
public MinimumAgeRequirement(int age) { Age = age; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CustomPolicyProvider.Controllers
|
||||
{
|
||||
public class AccountController: Controller
|
||||
{
|
||||
[HttpGet]
|
||||
public IActionResult Signin(string returnUrl = null)
|
||||
{
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
return View();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Signin(string userName, DateTime? birthDate, string returnUrl = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(userName)) return BadRequest("A user name is required");
|
||||
|
||||
// In a real-world application, user credentials would need validated before signing in
|
||||
var claims = new List<Claim>();
|
||||
// Add a Name claim and, if birth date was provided, a DateOfBirth claim
|
||||
claims.Add(new Claim(ClaimTypes.Name, userName));
|
||||
if (birthDate.HasValue)
|
||||
{
|
||||
claims.Add(new Claim(ClaimTypes.DateOfBirth, birthDate.Value.ToShortDateString()));
|
||||
}
|
||||
|
||||
// Create user's identity and sign them in
|
||||
var identity = new ClaimsIdentity(claims, "UserSpecified");
|
||||
await HttpContext.SignInAsync(new ClaimsPrincipal(identity));
|
||||
|
||||
return Redirect(returnUrl ?? "/");
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Signout()
|
||||
{
|
||||
await HttpContext.SignOutAsync();
|
||||
return Redirect("/");
|
||||
}
|
||||
|
||||
public IActionResult Denied()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace CustomPolicyProvider.Controllers
|
||||
{
|
||||
// Sample actions to demonstrate the use of the [MinimumAgeAuthorize] attribute
|
||||
[Controller]
|
||||
public class HomeController : Controller
|
||||
{
|
||||
public IActionResult Index()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
// View protected with custom parameterized authorization policy
|
||||
[MinimumAgeAuthorize(10)]
|
||||
public IActionResult MinimumAge10()
|
||||
{
|
||||
return View("MinimumAge", 10);
|
||||
}
|
||||
|
||||
// View protected with custom parameterized authorization policy
|
||||
[MinimumAgeAuthorize(50)]
|
||||
public IActionResult MinimumAge50()
|
||||
{
|
||||
return View("MinimumAge", 50);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.1;netcoreapp2.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore" Version="$(MicrosoftAspNetCorePackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.Cookies" Version="$(MicrosoftAspNetCoreAuthenticationCookiesPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="$(MicrosoftAspNetCoreAuthorizationPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="$(MicrosoftAspNetCoreMvcPackageVersion)" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
||||
namespace CustomPolicyProvider
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
CreateWebHostBuilder(args).Build().Run();
|
||||
}
|
||||
|
||||
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
|
||||
WebHost.CreateDefaultBuilder(args)
|
||||
.UseStartup<Startup>();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace CustomPolicyProvider
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
// Replace the default authorization policy provider with our own
|
||||
// custom provider which can return authorization policies for given
|
||||
// policy names (instead of using the default policy provider)
|
||||
services.AddSingleton<IAuthorizationPolicyProvider, MinimumAgePolicyProvider>();
|
||||
|
||||
// As always, handlers must be provided for the requirements of the authorization policies
|
||||
services.AddSingleton<IAuthorizationHandler, MinimumAgeAuthorizationHandler>();
|
||||
|
||||
services.AddMvc();
|
||||
|
||||
// Add cookie authentication so that it's possible to sign-in to test the
|
||||
// custom authorization policy behavior of the sample
|
||||
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
||||
.AddCookie(options =>
|
||||
{
|
||||
options.AccessDeniedPath = "/account/denied";
|
||||
options.LoginPath = "/account/signin";
|
||||
});
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
||||
{
|
||||
app.UseAuthentication();
|
||||
|
||||
app.UseMvc(routes =>
|
||||
{
|
||||
routes.MapRoute(
|
||||
name: "default",
|
||||
template: "{controller=Home}/{action=Index}");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
@using System.Security.Claims
|
||||
|
||||
<!-- Simple view used when a user doesn't have authorization to access a resource -->
|
||||
<h2>Access Denied: @(User?.Identity?.Name ?? "User") is not authorized to view this page.</h2>
|
||||
|
||||
<!-- Show the current user's date of birth (if such a claim exists) for debugging purposes -->
|
||||
@{
|
||||
var dateOfBirth = User?.FindFirst(c => c.Type == ClaimTypes.DateOfBirth)?.Value;
|
||||
}
|
||||
<h3>Date of birth: @(dateOfBirth ?? "<Undefined>")</h3>
|
||||
|
||||
<ul>
|
||||
<li>@Html.ActionLink("Home", "Index", "Home")</li>
|
||||
<li>@Html.ActionLink("Sign Out", "Signout", "Account")</li>
|
||||
</ul>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<h1>Sign In</h1>
|
||||
<div>
|
||||
<form asp-controller="Account" asp-action="Signin" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post">
|
||||
<div>
|
||||
<label>User Name</label>
|
||||
<input type="text" name="userName" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label>Date of Birth (MM/DD/YYYY)</label>
|
||||
<input type="text" name="birthDate" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type="submit">Sign In</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<h1>Custom Authorization Policy Provider Sample</h1>
|
||||
|
||||
<p>
|
||||
This sample demonstrates a custom IAuthorizationPolicyProvider which
|
||||
dynamically generates authorization policies based on arguments
|
||||
(in this case, an integer indicating the minimum age required to
|
||||
satisfy the policies requirements).
|
||||
</p>
|
||||
<p>
|
||||
Use the links below to sign in, sign out, or to try accessing pages
|
||||
requiring different minimum ages.
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>@Html.ActionLink("Sign In", "Signin", "Account")</li>
|
||||
<li>@Html.ActionLink("Sign Out", "Signout", "Account")</li>
|
||||
<li>@Html.ActionLink("Minimum Age 10", "MinimumAge10", "Home")</li>
|
||||
<li>@Html.ActionLink("Minimum Age 50", "MinimumAge50", "Home")</li>
|
||||
</ul>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
@using System.Security.Claims
|
||||
|
||||
@model int
|
||||
|
||||
@{
|
||||
var dateOfBirth = User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth)?.Value;
|
||||
}
|
||||
|
||||
<!-- Welcome user and display DateOfBirth claim value -->
|
||||
<h1>Welcome, @User.Identity.Name</h1>
|
||||
|
||||
<h2>Welcome to a page restricted to users @Model or older</h2>
|
||||
|
||||
<p>
|
||||
You can access this page since you were born on @dateOfBirth.
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li>@Html.ActionLink("Home", "Index", "Home")</li>
|
||||
<li>@Html.ActionLink("Sign Out", "Signout", "Account")</li>
|
||||
</ul>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
IAuthorizationPolicyProvider Sample
|
||||
===================================
|
||||
|
||||
This small sample demonstrates how to use `IAuthorizationPolicyProvider` to
|
||||
dynamically produce authorization policies.
|
||||
|
||||
In the simple example, a `MinimumAgePolicyProvider` will produce minimum age
|
||||
policies for any integer age (based on the policy's
|
||||
name). This demonstrates a slightly round-about way to allow 'parameterized'
|
||||
authorization policies. Since authorization policies are identified by
|
||||
name strings, the custom `MinimumAgeAuthorizeAttribute` in the sample
|
||||
allows users to specify an age parameter and then embeds it into its
|
||||
underlying `AuthorizationAttribute`'s policy name string. The
|
||||
`MinimumAgePolicyProvider` dynamically generates the policies needed for use
|
||||
with these attributes by pulling the age from the policy name and creating
|
||||
necessary authorization requirements.
|
||||
|
||||
Other uses of `IAuthorizationPolicyProvider` might be loading policy
|
||||
information from some external data source (like a database, for example).
|
||||
|
||||
Notice that ASP.NET Core only uses one authorization policy provider, so
|
||||
if not all policies will be generated by the custom policy provider, it
|
||||
should fall back to other policy providers (like `DefaultAuthorizationPolicyProvider`).
|
||||
|
||||
To use the sample:
|
||||
|
||||
1. Run the app
|
||||
2. Navigate to http://localhost:11606/
|
||||
3. Attempt to follow one of the 'Minimum Age' links
|
||||
4. Sign in by providing a user name and birth date
|
||||
5. Notice that depending on the birth date entered, pages guarded by minimum age authorization policies will either be accessible or forbidden
|
||||
|
||||
The interesting classes for this sample are in the Authorization folder,
|
||||
particularly `MinimumAgePolicyProvider`.
|
||||
Loading…
Reference in New Issue