Merge pull request #4727 from aspnet/master

Update live with current master
pull/4758/head^2
Rick Anderson 2017-11-03 11:19:12 -10:00 committed by GitHub
commit 41b73d6821
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
54 changed files with 1039 additions and 1 deletions

View File

@ -15,7 +15,7 @@
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute" />
<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
</environment>
@Html.Raw(JavaScriptSnippet.FullScript)
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">

View File

@ -409,4 +409,5 @@ services.AddMvc()
## See also
* [Razor Pages authorization conventions](xref:security/authorization/razor-pages-authorization)
* [Razor Pages custom route and page model providers](xref:mvc/razor-pages/razor-pages-convention-features)

View File

@ -290,3 +290,7 @@ The `ReplaceRouteValueFilter` attribute can be applied directly to a `PageModel`
Request the Page3 page from the sample app with at `localhost:5000/OtherPages/Page3/TriggerValue`. Notice how the filter replaces the route value:
![Request to OtherPages/Page3 with a TriggerValue route segment results in the filter replacing the route value with ReplacementValue.](razor-pages-convention-features/_static/otherpages-page3-filter-replacement-value.png)
## See also
* [Razor Pages authorization conventions](xref:security/authorization/razor-pages-authorization)

View File

@ -0,0 +1,79 @@
---
title: Razor Pages authorization conventions in ASP.NET Core
author: guardrex
description: Learn how to control access to pages with conventions at startup that authorize users and allow anonymous users to access individual pages or folders of pages.
ms.author: riande
manager: wpickett
ms.date: 10/27/2017
ms.topic: article
ms.assetid: f65ad22d-9472-478a-856c-c59c8681fa71
ms.technology: aspnet
ms.prod: asp.net-core
uid: security/authorization/razor-pages-authorization
---
# Razor Pages authorization conventions in ASP.NET Core
By [Luke Latham](https://github.com/guardrex)
One way to control access in your Razor Pages app is to use authorization conventions at startup. These conventions allow you to authorize users and allow anonymous users to access individual pages or folders of pages. The conventions described in this topic automatically apply [authorization filters](xref:mvc/controllers/filters#authorization-filters) to control access.
[View or download sample code](https://github.com/aspnet/Docs/tree/master/aspnetcore/security/authorization/razor-pages-authorization/sample) ([how to download](xref:tutorials/index#how-to-download-a-sample))
## Require authorization to access a page
Use the [AuthorizePage](/dotnet/api/microsoft.extensions.dependencyinjection.pageconventioncollectionextensions.authorizepage) convention via [AddRazorPagesOptions](/dotnet/api/microsoft.extensions.dependencyinjection.mvcrazorpagesmvcbuilderextensions.addrazorpagesoptions) to add an [AuthorizeFilter](/dotnet/api/microsoft.aspnetcore.mvc.authorization.authorizefilter) to the page at the specified path:
[!code-csharp[Main](razor-pages-authorization/sample/Startup.cs?name=snippet1&highlight=2,4)]
The specified path is the View Engine path, which is the Razor Pages root relative path without an extension and containing only forward slashes.
An [AuthorizePage overload](/dotnet/api/microsoft.extensions.dependencyinjection.pageconventioncollectionextensions.authorizepage#Microsoft_Extensions_DependencyInjection_PageConventionCollectionExtensions_AuthorizePage_Microsoft_AspNetCore_Mvc_ApplicationModels_PageConventionCollection_System_String_System_String_) is available if you need to specify an authorization policy.
## Require authorization to access a folder of pages
Use the [AuthorizeFolder](/dotnet/api/microsoft.extensions.dependencyinjection.pageconventioncollectionextensions.authorizefolder) convention via [AddRazorPagesOptions](/dotnet/api/microsoft.extensions.dependencyinjection.mvcrazorpagesmvcbuilderextensions.addrazorpagesoptions) to add an [AuthorizeFilter](/dotnet/api/microsoft.aspnetcore.mvc.authorization.authorizefilter) to all of the pages in a folder at the specified path:
[!code-csharp[Main](razor-pages-authorization/sample/Startup.cs?name=snippet1&highlight=2,5)]
The specified path is the View Engine path, which is the Razor Pages root relative path.
An [AuthorizeFolder overload](/dotnet/api/microsoft.extensions.dependencyinjection.pageconventioncollectionextensions.authorizefolder#Microsoft_Extensions_DependencyInjection_PageConventionCollectionExtensions_AuthorizeFolder_Microsoft_AspNetCore_Mvc_ApplicationModels_PageConventionCollection_System_String_System_String_) is available if you need to specify an authorization policy.
## Allow anonymous access to a page
Use the [AllowAnonymousToPage](/dotnet/api/microsoft.extensions.dependencyinjection.pageconventioncollectionextensions.allowanonymoustopage) convention via [AddRazorPagesOptions](/dotnet/api/microsoft.extensions.dependencyinjection.mvcrazorpagesmvcbuilderextensions.addrazorpagesoptions) to add an [AllowAnonymousFilter](/dotnet/api/microsoft.aspnetcore.mvc.authorization.allowanonymousfilter) to a page at the specified path:
[!code-csharp[Main](razor-pages-authorization/sample/Startup.cs?name=snippet1&highlight=2,6)]
The specified path is the View Engine path, which is the Razor Pages root relative path without an extension and containing only forward slashes.
## Allow anonymous access to a folder of pages
Use the [AllowAnonymousToFolder](/dotnet/api/microsoft.extensions.dependencyinjection.pageconventioncollectionextensions.allowanonymoustofolder) convention via [AddRazorPagesOptions](/dotnet/api/microsoft.extensions.dependencyinjection.mvcrazorpagesmvcbuilderextensions.addrazorpagesoptions) to add an [AllowAnonymousFilter](/dotnet/api/microsoft.aspnetcore.mvc.authorization.allowanonymousfilter) to all of the pages in a folder at the specified path:
[!code-csharp[Main](razor-pages-authorization/sample/Startup.cs?name=snippet1&highlight=2,7)]
The specified path is the View Engine path, which is the Razor Pages root relative path.
## Note on combining authorized and anonymous access
It's perfectly valid to specify that a folder of pages require authorization and specify that a page within that folder allows anonymous access:
```csharp
// This works.
.AuthorizeFolder("/Private").AllowAnonymousToPage("/Private/Public")
```
The reverse, however, isn't true. You can't declare a folder of pages for anonymous access and specify a page within for authorization:
```csharp
// This doesn't work!
.AllowAnonymousToFolder("/Public").AuthorizePage("/Public/Private")
```
Requiring authorization on the Private page won't work because when both the `AllowAnonymousFilter` and `AuthorizeFilter` filters are applied to the page, the `AllowAnonymousFilter` wins and controls access.
## See also
* [Razor Pages custom route and page model providers](xref:mvc/razor-pages/razor-pages-convention-features)
* [PageConventionCollection](/dotnet/api/microsoft.aspnetcore.mvc.applicationmodels.pageconventioncollection) class

View File

@ -0,0 +1,3 @@
{
"directory": "wwwroot/lib"
}

View File

@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,21 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
namespace AuthorizationSample.Controllers
{
[Route("[controller]/[action]")]
public class AccountController : Controller
{
[HttpPost]
public async Task<IActionResult> Logout()
{
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return RedirectToPage("/Account/SignedOut");
}
}
}

View File

@ -0,0 +1,8 @@
namespace AuthorizationSample.Data
{
public class ApplicationUser
{
public string Email { get; set; }
public string FullName { get; set; }
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Mvc
{
public static class UrlHelperExtensions
{
public static string GetLocalUrl(this IUrlHelper urlHelper, string localUrl)
{
if (!urlHelper.IsLocalUrl(localUrl))
{
return urlHelper.Page("/Index");
}
return localUrl;
}
}
}

View File

@ -0,0 +1,10 @@
@page
@model AboutModel
@{
ViewData["Title"] = "About";
}
<h1>@ViewData["Title"]</h1>
<h2>@Model.Message</h2>
<p>Use this area to provide additional information.</p>

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace AuthorizationSample.Pages
{
public class AboutModel : PageModel
{
public string Message { get; private set; }
public string RouteDataText { get; private set; }
public void OnGet()
{
Message = "Your application description page.";
}
}
}

View File

@ -0,0 +1,33 @@
@page
@model LoginModel
@{
ViewData["Title"] = "Log in";
}
<h1>@ViewData["Title"]</h1>
<div class="row">
<div class="col-md-3">
<form method="post">
<h2>Use a local account to log in.</h2>
<hr>
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.Email"></label>
<input asp-for="Input.Email" class="form-control">
<span asp-validation-for="Input.Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password" class="form-control">
<span asp-validation-for="Input.Password" class="text-danger"></span>
</div>
<div class="form-group">
<button type="submit" class="btn btn-default">Log in</button>
</div>
</form>
</div>
</div>
@section Scripts {
@await Html.PartialAsync("_ValidationScriptsPartial")
}

View File

@ -0,0 +1,106 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using AuthorizationSample.Data;
using Microsoft.AspNetCore.Authentication.Cookies;
using System.Collections.Generic;
namespace AuthorizationSample.Pages.Account
{
public class LoginModel : PageModel
{
[BindProperty]
public InputModel Input { get; set; }
public string ReturnUrl { get; private set; }
[TempData]
public string ErrorMessage { get; set; }
public class InputModel
{
[Required]
[EmailAddress]
public string Email { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
public async Task OnGetAsync(string returnUrl = null)
{
if (!string.IsNullOrEmpty(ErrorMessage))
{
ModelState.AddModelError(string.Empty, ErrorMessage);
}
// Clear the existing external cookie
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
ReturnUrl = returnUrl;
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
ReturnUrl = returnUrl;
if (ModelState.IsValid)
{
var user = await AuthenticateUser(Input.Email, Input.Password);
if (user == null)
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.Email),
new Claim("FullName", user.FullName)
};
var claimsIdentity = new ClaimsIdentity(
claims, CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity));
return LocalRedirect(Url.GetLocalUrl(returnUrl));
}
// Something failed. Redisplay the form.
return Page();
}
private async Task<ApplicationUser> AuthenticateUser(string email, string password)
{
// For demonstration purposes, authenticate a user
// with a static email address. Ignore the password.
// Assume that checking the database takes 500ms
await Task.Delay(500);
if (Input.Email == "maria.rodriguez@contoso.com")
{
return new ApplicationUser()
{
Email = "maria.rodriguez@contoso.com",
FullName = "Maria Rodriguez"
};
}
else
{
return null;
}
}
}
}

View File

@ -0,0 +1,10 @@
@page
@model SignedOutModel
@{
ViewData["Title"] = "Signed out";
}
<h1>@ViewData["Title"]</h1>
<p>
You have successfully signed out.
</p>

View File

@ -0,0 +1,20 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace AuthorizationSample.Pages.Account
{
public class SignedOutModel : PageModel
{
public IActionResult OnGet()
{
if (User.Identity.IsAuthenticated)
{
// Redirect to home page if the user is authenticated.
return RedirectToPage("/Index");
}
return Page();
}
}
}

View File

@ -0,0 +1 @@
@using AuthorizationSample.Pages.Account

View File

@ -0,0 +1,27 @@
@page
@model ContactModel
@{
ViewData["Title"] = "Contact";
}
<h1>@ViewData["Title"]</h1>
<h2>@Model.Message</h2>
<p>
This page requires authorization by convention:
<code>
options.Conventions.AuthorizePage("/Contact");
</code>
</p>
<address>
One Microsoft Way<br>
Redmond, WA 98052-6399<br>
<abbr title="Phone">P:</abbr>
425.555.0100
</address>
<address>
<strong>Support:</strong> <a href="mailto:Support@example.com">Support@example.com</a><br>
<strong>Marketing:</strong> <a href="mailto:Marketing@example.com">Marketing@example.com</a>
</address>

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace AuthorizationSample.Pages
{
public class ContactModel : PageModel
{
public string Message { get; private set; }
public void OnGet()
{
Message = "Your contact page.";
}
}
}

View File

@ -0,0 +1,23 @@
@page
@model ErrorModel
@{
ViewData["Title"] = "Error";
}
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (Model.ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@Model.RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>Development environment should not be enabled in deployed applications</strong>, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong> and restarting the app or adding <code>.UseEnvironment("Development")</code> to <code>WebHost</code> in <i>Program.cs</i> and restarting the app.
</p>

View File

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace AuthorizationSample.Pages
{
public class ErrorModel : PageModel
{
public string RequestId { get; private set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
}

View File

@ -0,0 +1,120 @@
@page
@model IndexModel
@{
ViewData["Title"] = "Home page";
}
@if (User.Identity.IsAuthenticated)
{
<div class="row">
<div class="col-md-12">
<div class="welcome">Hello @User.Claims.FirstOrDefault(c => c.Type == "FullName")?.Value!</div>
<p>Username: @User.Identity.Name</p>
</div>
</div>
}
<div id="myCarousel" class="carousel slide" data-ride="carousel" data-interval="6000">
<ol class="carousel-indicators">
<li data-target="#myCarousel" data-slide-to="0" class="active"></li>
<li data-target="#myCarousel" data-slide-to="1"></li>
<li data-target="#myCarousel" data-slide-to="2"></li>
<li data-target="#myCarousel" data-slide-to="3"></li>
</ol>
<div class="carousel-inner" role="listbox">
<div class="item active">
<img src="~/images/banner1.svg" alt="ASP.NET" class="img-responsive" />
<div class="carousel-caption" role="option">
<p>
Learn how to build ASP.NET apps that can run anywhere.
<a class="btn btn-default" href="https://go.microsoft.com/fwlink/?LinkID=525028&clcid=0x409">
Learn More
</a>
</p>
</div>
</div>
<div class="item">
<img src="~/images/banner2.svg" alt="Visual Studio" class="img-responsive" />
<div class="carousel-caption" role="option">
<p>
There are powerful new features in Visual Studio for building modern web apps.
<a class="btn btn-default" href="https://go.microsoft.com/fwlink/?LinkID=525030&clcid=0x409">
Learn More
</a>
</p>
</div>
</div>
<div class="item">
<img src="~/images/banner3.svg" alt="Package Management" class="img-responsive" />
<div class="carousel-caption" role="option">
<p>
Bring in libraries from NuGet, Bower, and npm, and automate tasks using Grunt or Gulp.
<a class="btn btn-default" href="https://go.microsoft.com/fwlink/?LinkID=525029&clcid=0x409">
Learn More
</a>
</p>
</div>
</div>
<div class="item">
<img src="~/images/banner4.svg" alt="Microsoft Azure" class="img-responsive" />
<div class="carousel-caption" role="option">
<p>
Learn how Microsoft's Azure cloud platform allows you to build, deploy, and scale web apps.
<a class="btn btn-default" href="https://go.microsoft.com/fwlink/?LinkID=525027&clcid=0x409">
Learn More
</a>
</p>
</div>
</div>
</div>
<a class="left carousel-control" href="#myCarousel" role="button" data-slide="prev">
<span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="right carousel-control" href="#myCarousel" role="button" data-slide="next">
<span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</div>
<div class="row">
<div class="col-md-3">
<h1>Application uses</h1>
<ul>
<li>Sample pages using ASP.NET Core Razor Pages</li>
<li><a href="https://go.microsoft.com/fwlink/?LinkId=518004">Bower</a> for managing client-side libraries</li>
<li>Theming using <a href="https://go.microsoft.com/fwlink/?LinkID=398939">Bootstrap</a></li>
</ul>
</div>
<div class="col-md-3">
<h1>How to</h1>
<ul>
<li><a href="https://go.microsoft.com/fwlink/?linkid=852130">Working with Razor Pages.</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkId=699315">Manage User Secrets using Secret Manager.</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkId=699316">Use logging to log a message.</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkId=699317">Add packages using NuGet.</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkId=699318">Add client packages using Bower.</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkId=699319">Target development, staging or production environment.</a></li>
</ul>
</div>
<div class="col-md-3">
<h1>Overview</h1>
<ul>
<li><a href="https://go.microsoft.com/fwlink/?LinkId=518008">Conceptual overview of what is ASP.NET Core</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkId=699320">Fundamentals of ASP.NET Core such as Startup and middleware.</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkId=398602">Working with Data</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkId=398603">Security</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkID=699321">Client side development</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkID=699322">Develop on different platforms</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkID=699323">Read more on the documentation site</a></li>
</ul>
</div>
<div class="col-md-3">
<h1>Run &amp; Deploy</h1>
<ul>
<li><a href="https://go.microsoft.com/fwlink/?LinkID=517851">Run your app</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkID=517853">Run tools such as EF migrations and more</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkID=398609">Publish to Microsoft Azure App Service</a></li>
</ul>
</div>
</div>

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace AuthorizationSample.Pages
{
public class IndexModel : PageModel
{
public void OnGet()
{
}
}
}

View File

@ -0,0 +1,16 @@
@page
@namespace AuthorizationSample.Pages.Private
@model PrivatePage1Model
@{
ViewData["Title"] = "Private Folder: Private Page 1";
}
<h1>@ViewData["Title"]</h1>
<h2>@Model.Message</h2>
<p>
This page requires authorization by convention:
<code>
options.Conventions.AuthorizeFolder("/Private");
</code>
</p>

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace AuthorizationSample.Pages.Private
{
public class PrivatePage1Model : PageModel
{
public string Message { get; private set; }
public void OnGet()
{
Message = "A private page inside the Private folder.";
}
}
}

View File

@ -0,0 +1,16 @@
@page
@namespace AuthorizationSample.Pages.Private
@model PrivatePage2Model
@{
ViewData["Title"] = "Private Folder: Private Page 2";
}
<h1>@ViewData["Title"]</h1>
<h2>@Model.Message</h2>
<p>
This page requires authorization by convention:
<code>
options.Conventions.AuthorizeFolder("/Private");
</code>
</p>

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace AuthorizationSample.Pages.Private
{
public class PrivatePage2Model : PageModel
{
public string Message { get; private set; }
public void OnGet()
{
Message = "A private page inside the Private folder.";
}
}
}

View File

@ -0,0 +1,16 @@
@page
@namespace AuthorizationSample.Pages.Private
@model PublicPageModel
@{
ViewData["Title"] = "Private Folder: Public Page";
}
<h1>@ViewData["Title"]</h1>
<h2>@Model.Message</h2>
<p>
The <i>Private</i> folder requires authorization, but this page allows anonymous visitors by convention:
<code>
options.Conventions.AllowAnonymousToPage("/Private/PublicPage");
</code>
</p>

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace AuthorizationSample.Pages.Private
{
public class PublicPageModel : PageModel
{
public string Message { get; private set; }
public void OnGet()
{
Message = "A public page inside the Private folder.";
}
}
}

View File

@ -0,0 +1,16 @@
@page
@namespace AuthorizationSample.Pages.Private.Public
@model PublicPageModel
@{
ViewData["Title"] = "Private Folder: PublicPages Folder: Public Page";
}
<h1>@ViewData["Title"]</h1>
<h2>@Model.Message</h2>
<p>
The <i>Private</i> folder requires authorization, but this page is inside a <i>PublicPages</i> folder that allows anonymous visitors by convention:
<code>
options.Conventions.AllowAnonymousToFolder("/Private/PublicPages");
</code>
</p>

View File

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace AuthorizationSample.Pages.Private.Public
{
public class PublicPageModel : PageModel
{
public string Message { get; private set; }
public void OnGet()
{
Message = "A public page inside a PublicPages folder inside the Private folder.";
}
}
}

View File

@ -0,0 +1 @@
@using AuthorizationSample.Pages.Private.Public

View File

@ -0,0 +1 @@
@using AuthorizationSample.Pages.Private

View File

@ -0,0 +1,82 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - AuthorizationSample</title>
<environment include="Development">
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" />
</environment>
<environment exclude="Development">
<link rel="stylesheet" href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/css/bootstrap.min.css"
asp-fallback-href="~/lib/bootstrap/dist/css/bootstrap.min.css"
asp-fallback-test-class="sr-only" asp-fallback-test-property="position" asp-fallback-test-value="absolute"
crossorigin="anonymous"
integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u">
<link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true">
</environment>
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a asp-page="/Index" class="navbar-brand">AuthorizationSample</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li><a asp-page="/Index">Home</a></li>
<li>
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Pages<b class="caret"></b></a>
<ul class="dropdown-menu multi-level">
<li><a asp-page="/Private/PrivatePage1">Private Folder: Private Page 1 (Authentication Required)</a></li>
<li><a asp-page="/Private/PrivatePage2">Private Folder: Private Page 2 (Authentication Required)</a></li>
<li><a asp-page="/Private/PublicPage">Private Folder: Public Page</a></li>
<li><a asp-page="/Private/PublicPages/PublicPage">Private Folder: Public Folder: Public Page</a></li>
</ul>
</li>
<li><a asp-page="/About">About</a></li>
<li><a asp-page="/Contact">Contact (Authentication Required)</a></li>
</ul>
@await Html.PartialAsync("_LoginPartial")
</div>
</div>
</nav>
<div class="container body-content">
@RenderBody()
<hr>
<footer>
<p>&copy;@System.DateTime.Now.Year - AuthorizationSample</p>
</footer>
</div>
<environment include="Development">
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
</environment>
<environment exclude="Development">
<script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-2.2.0.min.js"
asp-fallback-src="~/lib/jquery/dist/jquery.min.js"
asp-fallback-test="window.jQuery"
crossorigin="anonymous"
integrity="sha384-K+ctZQ+LL8q6tP7I94W+qzQsfRV2a+AfHIi9k8z8l9ggpc8X+Ytst4yBo/hH+8Fk">
</script>
<script src="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/bootstrap.min.js"
asp-fallback-src="~/lib/bootstrap/dist/js/bootstrap.min.js"
asp-fallback-test="window.jQuery && window.jQuery.fn && window.jQuery.fn.modal"
crossorigin="anonymous"
integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa">
</script>
<script src="~/js/site.min.js" asp-append-version="true"></script>
</environment>
@RenderSection("Scripts", required: false)
</body>
</html>

View File

@ -0,0 +1,16 @@
@if (User.Identity.IsAuthenticated)
{
<form asp-controller="Account" asp-action="Logout" method="post" id="logoutForm" class="navbar-right">
<ul class="nav navbar-nav navbar-right">
<li>
<button type="submit" class="btn btn-link navbar-btn navbar-link">Log out</button>
</li>
</ul>
</form>
}
else
{
<ul class="nav navbar-nav navbar-right">
<li><a asp-page="/Account/Login">Log in</a></li>
</ul>
}

View File

@ -0,0 +1,18 @@
<environment include="Development">
<script src="~/lib/jquery-validation/dist/jquery.validate.js"></script>
<script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script>
</environment>
<environment exclude="Development">
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validate/1.14.0/jquery.validate.min.js"
asp-fallback-src="~/lib/jquery-validation/dist/jquery.validate.min.js"
asp-fallback-test="window.jQuery && window.jQuery.validator"
crossorigin="anonymous"
integrity="sha384-Fnqn3nxp3506LP/7Y3j/25BlWeA3PXTyT1l78LjECcPaKCV12TsZP7yyMxOe/G/k">
</script>
<script src="https://ajax.aspnetcdn.com/ajax/jquery.validation.unobtrusive/3.2.6/jquery.validate.unobtrusive.min.js"
asp-fallback-src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"
asp-fallback-test="window.jQuery && window.jQuery.validator && window.jQuery.validator.unobtrusive"
crossorigin="anonymous"
integrity="sha384-JrXK+k53HACyavUKOsL+NkmSesD2P+73eDMrbTtTk0h4RmOF8hF8apPlkp26JlyH">
</script>
</environment>

View File

@ -0,0 +1,4 @@
@using AuthorizationSample
@using AuthorizationSample.Data
@namespace AuthorizationSample.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace AuthorizationSample
{
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
}

View File

@ -0,0 +1,16 @@
# ASP.NET Core Authorization Sample
This sample illustrates use of Razor Pages authorization by conventions. This sample demonstrates the features described in the [Razor Pages authorization conventions](https://docs.microsoft.com/aspnet/core/security/authorization/razor-pages-authorization) topic.
User authorization in this sample uses the cookie authentication features described in the [Using Cookie Authentication without ASP.NET Core Identity](https://docs.microsoft.com/aspnet/core/security/authentication/cookie) topic. For information on using ASP.NET Core Identity, see the topics in the [Authentication](https://docs.microsoft.com/aspnet/core/security/authentication/index) section of the documentation.
When running the sample, use the email address **maria.rodriguez@contoso.com** to authenticate the user.
## Examples in this sample
| Feature | Description |
| ------- | ----------- |
| [AuthorizePage](https://docs.microsoft.com/dotnet/api/microsoft.extensions.dependencyinjection.pageconventioncollectionextensions.authorizepage) | Adds an [AuthorizeFilter](https://docs.microsoft.com/dotnet/api/microsoft.aspnetcore.mvc.authorization.authorizefilter) to the page with the specified path. |
| [AuthorizeFolder](https://docs.microsoft.com/dotnet/api/microsoft.extensions.dependencyinjection.pageconventioncollectionextensions.authorizefolder) | Adds an [AuthorizeFilter](https://docs.microsoft.com/dotnet/api/microsoft.aspnetcore.mvc.authorization.authorizefilter) to all of the pages in a folder with the specified path. |
| [AllowAnonymousToPage](https://docs.microsoft.com/dotnet/api/microsoft.extensions.dependencyinjection.pageconventioncollectionextensions.allowanonymoustopage) | Adds an [AllowAnonymousFilter](https://docs.microsoft.com/dotnet/api/microsoft.aspnetcore.mvc.authorization.allowanonymousfilter) to a page with the specified path. |
| [AllowAnonymousToFolder](https://docs.microsoft.com/dotnet/api/microsoft.extensions.dependencyinjection.pageconventioncollectionextensions.allowanonymoustofolder) | Adds an [AllowAnonymousFilter](https://docs.microsoft.com/dotnet/api/microsoft.aspnetcore.mvc.authorization.allowanonymousfilter) to all of the pages in a folder with the specified path. |

View File

@ -0,0 +1,49 @@
using System;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.Extensions.DependencyInjection;
namespace AuthorizationSample
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
#region snippet1
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizePage("/Contact");
options.Conventions.AuthorizeFolder("/Private");
options.Conventions.AllowAnonymousToPage("/Private/PublicPage");
options.Conventions.AllowAnonymousToFolder("/Private/PublicPages");
});
#endregion
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc();
}
}
}

View File

@ -0,0 +1,10 @@
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
}

View File

@ -0,0 +1,8 @@
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
}
}

View File

@ -0,0 +1,10 @@
{
"name": "asp.net",
"private": true,
"dependencies": {
"bootstrap": "3.3.7",
"jquery": "2.2.0",
"jquery-validation": "1.14.0",
"jquery-validation-unobtrusive": "3.2.6"
}
}

View File

@ -0,0 +1,24 @@
// Configure bundling and minification for the project.
// More info at https://go.microsoft.com/fwlink/?LinkId=808241
[
{
"outputFileName": "wwwroot/css/site.min.css",
// An array of relative input file paths. Globbing patterns supported
"inputFiles": [
"wwwroot/css/site.css"
]
},
{
"outputFileName": "wwwroot/js/site.min.js",
"inputFiles": [
"wwwroot/js/site.js"
],
// Optionally specify minification options
"minify": {
"enabled": true,
"renameLocals": true
},
// Optionally generate .map file
"sourceMap": false
}
]

View File

@ -0,0 +1,48 @@
body {
padding-top: 50px;
padding-bottom: 20px
}
h1 {
font-size: 30px
}
h2 {
font-size: 24px
}
/* Wrapping element */
/* Set some basic padding to keep content from hitting the edges */
.body-content {
padding-left: 15px;
padding-right: 15px
}
/* Carousel */
.carousel-caption p {
font-size: 20px;
line-height: 1.4
}
/* Make .svg files in the carousel display properly in older browsers */
.carousel-inner .item img[src$=".svg"] {
width: 100%
}
/* QR code generator */
#qrCode {
margin: 15px
}
.welcome {
font-size:2.4em;
margin-top:6px
}
/* Hide/rearrange for smaller screens */
@media screen and (max-width: 767px) {
/* Hide captions */
.carousel-caption {
display: none
}
}

View File

@ -0,0 +1 @@
body{padding-top:50px;padding-bottom:20px}h1{font-size:30px}h2{font-size:24px}.body-content{padding-left:15px;padding-right:15px}.carousel-caption p{font-size:20px;line-height:1.4}.carousel-inner .item img[src$=".svg"]{width:100%}#qrCode{margin:15px}.welcome{font-size:2.4em;margin-top:6px}@media screen and (max-width:767px){.carousel-caption{display:none}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1 @@
// Write your Javascript code.

View File

@ -228,6 +228,7 @@
## [Authorization](xref:security/authorization/index)
### [Introduction](xref:security/authorization/introduction)
### [Create an app with user data protected by authorization](xref:security/authorization/secure-data)
### [Razor Pages authorization](xref:security/authorization/razor-pages-authorization)
### [Simple Authorization](xref:security/authorization/simple)
### [Role based Authorization](xref:security/authorization/roles)
### [Claims-Based Authorization](xref:security/authorization/claims)