Razor Pages是在ASP.NET Core中创建Web应用程序的编程模型之一。让我们看看如何使用Auth0 ASP.NET Core Authentication SDK添加认证支持。
Razor Pages与ASP.NET Core MVC的对比
当谈到用ASP.NET构建Web应用程序时,你会发现自己不得不在几种编程模型中做出选择。抛开单页应用(SPA),只关注.NET的最新版本,你有两种编程模型来创建传统的Web应用。ASP.NET Core MVC和Razor Pages。你应该为你的网络应用程序选择哪种编程模型?
ASP.NET Core MVC模型比Razor Pages模型更受欢迎,也许是因为ASP.NET Core MVC有更悠久的传统,它始于2009年的ASP.NET MVC。实际上,这并不意味着这种编程模型比Razor Pages模型更好。
这两种编程模型都依赖于相同的模板引擎--Razor。然而,ASP.NET Core MVC提倡模型-视图-控制器(MVC)设计模式,而Razor Pages应用程序提出了一种更轻、更注重页面的方法。因此,当你不得不选择为你的网络应用程序使用哪种编程模式时,你应该仔细评估你的应用程序的行为适合在哪里。正如微软文档中所说,"如果你的ASP.NET MVC应用大量使用视图,你可能要考虑从动作和视图迁移到Razor Pages"。
也就是说,如果你有使用这两种编程模型的经验,你可能会注意到这两种模型之间的边界不是那么整齐。由于这两种模型共享相同的模板引擎,你可能会发现ASP.NET Core MVC应用程序在需要一个简单页面时使用Razor Pages。在另一边,你可能会发现Razor Pages应用程序在有意义的地方使用控制器的功能。
混合这两种模式可以让你取其精华,构建高效的Web应用。
要了解更多关于Razor Pages的信息,请查看官方文档。本文将重点介绍用Auth0保护Razor Pages应用程序的安全。如果你想在ASP.NET Core MVC应用程序中添加认证,请查看这篇文章。
示例应用程序
本文不会让你从头开始建立一个Razor Pages应用程序。相反,你将修改一个用C# 10构建的现有样本项目。这意味着你需要在你的机器上安装.NET 6 SDK。要了解更多关于.NET 6所引入的新功能,请查看这篇文章。
虽然本文的说明将促使你使用.NET CLI来构建和运行应用程序,但如果你愿意,你也可以使用Visual Studio 2022。
获取并运行示例应用程序
你可以在终端窗口中运行以下命令,在你的机器上下载示例应用程序。
git clone -b starter --single-branch https://github.com/auth0-blog/acme-aspnet-razor.git
一旦你下载了它,移动到 acme-aspnet-razor文件夹,并键入以下命令来启动该应用程序。
dotnet watch
该命令将运行样本应用程序,并等待对代码可能的修改。如果你改变了应用程序的代码,它将被自动重新构建。
请注意,对你的代码的一些特定的改变,即所谓的粗暴的编辑,可能需要重新启动你的应用程序。阅读这个可以了解更多。
几秒钟后,你的应用程序就启动并运行了。将你的浏览器指向 https://localhost:7204.你应该看到以下页面。

这是虚构的公司ACME公司的主页。
通过点击标题中的目录链接,你可以浏览他们的目录,看起来如下所示。

实际上,"*现在购买 "*按钮并不工作。这个页面只是一个占位符,用户会期望这个页面受到保护。换句话说,只有经过认证的用户才能访问这个目录页面。这就是你在接下来的几节中要实现的。
向Auth0注册
在做任何修改之前,你必须在Auth0注册该应用程序。当然,你需要一个Auth0账户。如果你还没有一个,你可以免费注册。
一旦进入仪表板,移到应用程序部分,并遵循这些步骤。
- 单击 "创建应用程序"。
- 为你的应用程序提供一个友好的名称(例如,ACME Web App),并选择常规Web应用程序作为应用程序类型。
- 最后,点击创建按钮。
这些步骤使Auth0知道你的ASP.NET Core应用程序,并将允许你控制对它的访问。
应用程序创建后,移到设置选项卡,注意你的Auth0域和你的客户ID。我们很快就会用到它们。
然后,在同一个表格中,将值分配给 https://localhost:7204/callback到允许的回调URLs字段,并将值 https://localhost:7204/到允许注销的URLs字段。
第一个值告诉Auth0在用户认证后要回调哪个URL。第二个值告诉Auth0,用户在注销后应该被重定向到哪个URL。
点击 "保存更改"按钮来应用它们。
现在,回到示例应用程序项目的根文件夹,打开 appsettings.json配置文件,并将其内容替换为以下内容。
// appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Auth0": {
"Domain": "YOUR_DOMAIN",
"ClientId": "YOUR_CLIENT_ID"
}
}
替换占位符 YOUR_DOMAIN和 YOUR_CLIENT_ID替换为从Auth0仪表板上获取的相应值。
添加认证
在这一点上,连接你的应用程序和Auth0的基本设置已经到位。要添加认证,你需要对你的应用程序进行一些修改,并使用这些设置。让我们一步步来。
安装Auth0 SDK
第一步,在终端窗口运行以下命令,安装Auth0 ASP.NET Core Authentication SDK。
dotnet add package Auth0.AspNetCore.Authentication
Auth0 ASP.NET Core Authentication SDK可以让你轻松地将基于OpenID Connect的认证整合到你的应用程序中,而不需要处理所有低级别的细节。
如果你想了解更多,这篇博文为你提供了Auth0 ASP.NET Core Authentication SDK的概述。
设置认证
现在,让我们修改应用程序代码以支持认证。打开该 Program.cs文件,对其内容做如下修改。
// Program.cs
using Auth0.AspNetCore.Authentication; // 👈 new code
var builder = WebApplication.CreateBuilder(args);
// 👇 new code
builder.Services
.AddAuth0WebAppAuthentication(options => {
options.Domain = builder.Configuration["Auth0:Domain"];
options.ClientId = builder.Configuration["Auth0:ClientId"];
});
// 👆 new code
// Add services to the container.
builder.Services.AddRazorPages();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication(); // 👈 new code
app.UseAuthorization();
app.MapRazorPages();
app.Run();
在突出显示的代码之后,你在文件的开头添加了一个对 Auth0.AspNetCore.Authentication命名空间的引用,在文件的开头。然后你调用了 AddAuth0WebAppAuthentication()方法,将Auth0域和客户ID作为参数。最后,你调用了 UseAuthentication()方法来启用认证中间件。请确保在调用 UseAuthentication()之前UseAuthorization().
这些变化为支持通过Auth0认证奠定了基础。
实施登录
为了实现登录,请移动到项目的根文件夹,在终端窗口运行以下命令,添加一个新的Razor页面。
dotnet new page --name Login --namespace acme.Pages --output Pages/Account
这个命令将在Pages 文件夹中创建一个Account 文件夹,并在那里添加两个文件。 Login.cshtml和 Login.cshtml.cs.打开该 Login.cshtml.cs文件并将其内容替换为以下内容。
// Pages/Account/Login.cshtml.cs
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Authentication;
using Auth0.AspNetCore.Authentication;
namespace acme.Pages;
public class LoginModel : PageModel
{
public async Task OnGet(string returnUrl = "/")
{
var authenticationProperties = new LoginAuthenticationPropertiesBuilder()
.WithRedirectUri(returnUrl)
.Build();
await HttpContext.ChallengeAsync(Auth0Constants.AuthenticationScheme, authenticationProperties);
}
}
这个类定义了登录页面的页面模型。实际上,这个页面根本就没有显示。它只是创建了一套登录所需的认证属性,并通过Auth0触发了认证过程。
为了启用这个登录操作,你需要改变用户界面。因此,打开 _Layout.cshtml文件夹下的 Pages/Shared文件夹下的文件,并更新其内容如下。
@* Pages/Shared/_Layout.cshtml *@
<!DOCTYPE html>
<html lang="en">
<!-- ...existing code -->
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
// 👇 new code
@if (User.Identity.IsAuthenticated)
{
// 👆 new code
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark display-6" asp-area=""
asp-page="/Catalog">Catalog</a>
</li>
</ul>
// 👇 new code
} else {
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link text-dark display-6" asp-area=""
asp-page="Account/Login">Login</a>
</li>
</ul>
}
// 👆 new code
</div>
<!-- ...existing code -->
你修改页面的标记,在 User.Identity.IsAuthenticated属性的检查。正如你可能已经猜到的那样,这个属性让你知道当前用户是否经过认证。如果用户没有通过认证,导航栏的右侧将显示一个登录链接。
测试登录
一切都准备好了。在浏览器中刷新页面,你应该看到登录链接,如下图所示。

当你点击该链接时,你将被重定向到Auth0通用登录页面,如下截图所示。

用户第一次认证时,会被提示有一个类似以下的页面。

这个页面被称为同意屏幕,它通知用户,样本应用程序将访问他们的用户资料数据。接受后,你会被重定向到样本应用程序,应该看到导航栏左侧出现了目录链接。

祝贺你!你在你的ASP.NET应用程序中添加了认证。你在你的ASP.NET Core Razor Pages应用程序中添加了认证!
保护私人页面
尽管你必须通过认证才能看到导航栏中的目录链接,但目录视图本身并没有受到保护。你可以通过访问 https://localhost:7204/Catalog地址来验证。
你需要限制只有通过认证的用户才能访问目录。
保护目录页
为此目的,编辑 Program.cs文件,如下图所示。
// Program.cs
using Auth0.AspNetCore.Authentication;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddAuth0WebAppAuthentication(options => {
options.Domain = builder.Configuration["Auth0:Domain"];
options.ClientId = builder.Configuration["Auth0:ClientId"];
});
// Add services to the container.
// 👇 changed code
builder.Services.AddRazorPages(options =>
{
options.Conventions.AuthorizePage("/Catalog");
});
// 👆 changed code
var app = builder.Build();
//...existing code...
你使用了Razor Pages授权约定来保护目录页面不被非法访问。这种方法允许你在你的 Razor Pages 应用程序中集中访问控制。请查看官方文档以了解更多关于Razor Pages授权约定的信息。
实现注销
使你的应用程序可用的另一个缺失的东西是注销。事实上,目前,当你登录到应用程序时,你会保持登录状态,直到你的会话过期。你可能想让你的用户明确地注销应用程序。为此,通过运行以下命令在Account 文件夹中添加一个注销页面。
dotnet new page --name Logout --namespace acme.Pages --output Pages/Account
然后,打开该 Logout.cshtml.cs文件并将其内容替换为以下内容。
// Pages/Account/Logout.cshtml.cs
using Microsoft.AspNetCore.Mvc.RazorPages;
using Auth0.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
namespace acme.Pages
{
public class LogoutModel : PageModel
{
public async Task OnGet()
{
var authenticationProperties = new LogoutAuthenticationPropertiesBuilder()
.WithRedirectUri("/")
.Build();
await HttpContext.SignOutAsync(Auth0Constants.AuthenticationScheme, authenticationProperties);
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
}
}
和登录页面一样,注销页面也不显示任何东西。它只是创建一组所需的属性,并触发注销过程,销毁Auth0会话和本地会话。
下一步是使注销链接对用户可用。因此,更新以下文件的内容 _Layout.cshtml文件夹下的 Pages/Shared文件夹下的文件内容如下。
@* Pages/Shared/_Layout.cshtml *@
<!DOCTYPE html>
<html lang="en">
<!-- ...existing code -->
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
@if (User.Identity.IsAuthenticated)
{
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark display-6" asp-area=""
asp-page="/Catalog">Catalog</a>
</li>
</ul>
// 👇 new code
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link text-dark display-6" asp-area=""
asp-page="/Account/Logout">Logout</a>
</li>
</ul>
// 👆 new code
} else {
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link text-dark display-6" asp-area=""
asp-page="/Account/Login">Login</a>
</li>
</ul>
}
</div>
<!-- ...existing code -->
你只是在用户被认证时,在导航栏的右侧添加了注销链接。该链接指向Account 文件夹中的注销页面。
作为最后一步,打开 Program.cs文件,并将注销页面配置为受保护的页面,如下图所示。
// Program.cs
//...existing code...
// Add services to the container.
builder.Services.AddRazorPages(options =>
{
options.Conventions.AuthorizePage("/Catalog");
// 👇 new code
options.Conventions.AuthorizePage("/Account/Logout");
// 👆 new code
});
//...existing code...
测试应用程序
现在是测试这个新版本的应用程序的时候了。首先,从你的浏览器中删除所有的cookies或使用隐身窗口。这是需要的,因为到目前为止你还没有机会注销。
现在,在你再次登录后,主页应该看起来如下。

当你点击注销链接时,你将与Auth0断开连接,看到通常的主页,只有登录链接。
通过对一些最臭名昭著的威胁的实践探索来学习网络安全。

添加个人资料页
让我们试着更进一步,添加一个显示用户的一些数据的页面。
指定你的作用域
如前所述,Auth0 ASP.NET Core Authentication SDK使用OpenID Connect(OIDC)来验证你的用户。OIDC为你的应用程序提供了一个ID令牌,其中包含了一些关于用户的基本数据。从技术角度来看,这些用户数据是可用的,因为SDK默认会请求openid 和profile 范围。你没有看到这一点,因为SDK负责整个认证过程并管理ID令牌。所以,默认情况下,你有用户的名字,可能还有他们的照片。如果你还想让用户的电子邮件出现在他们的资料中,你必须明确指定范围。
打开该 Program.cs文件,并应用以下修改。
// Program.cs
// ...existing code...
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddAuth0WebAppAuthentication(options => {
options.Domain = builder.Configuration["Auth0:Domain"];
options.ClientId = builder.Configuration["Auth0:ClientId"];
options.Scope = "openid profile email"; // 👈 new code
});
// ...existing code...
你给Scope 选项分配了一个字符串,包含前面提到的默认作用域(openid 和profile )和email 作用域。
这将导致电子邮件被添加到用户资料中。
创建个人资料页面
现在,让我们创建一个新的页面,以显示用户的个人资料数据。从项目的根文件夹中,运行以下命令。
dotnet new page --name Profile --namespace acme.Pages --output Pages/Account
像往常一样,它在文件夹中创建了两个新的文件 Pages/Account文件夹中。 Profile.cshtml和 Profile.cshtml.cs.
打开这个 Profile.cshtml.cs文件并将其内容替换为以下内容。
// Pages/Account/Profile.cshtml.cs
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Security.Claims;
namespace acme.Pages;
public class ProfileModel : PageModel
{
public string UserName { get; set; }
public string UserEmailAddress { get; set; }
public string UserProfileImage { get; set; }
public void OnGet()
{
UserName = User.Identity.Name;
UserEmailAddress = User.FindFirst(c => c.Type == ClaimTypes.Email)?.Value;
UserProfileImage = User.FindFirst(c => c.Type == "picture")?.Value;
}
}
这段代码简单地定义了ProfileModel 类,包含了用户资料的三个元素:姓名、电子邮件地址和用户的照片。
现在,定义相关的视图,将同一文件夹中的 Profile.cshtml文件的内容。
@* Pages/Account/Profile.cshtml *@
@page
@model acme.Pages.ProfileModel
@{
ViewData["Title"] = "User Profile";
}
<div class="row">
<div class="col-md-12">
<div class="row">
<h2>@ViewData["Title"].</h2>
<div class="col-md-2">
<img src="@Model.UserProfileImage"
alt="" class="img-rounded img-responsive" />
</div>
<div class="col-md-4">
<h3>@Model.UserName</h3>
<p>
<i class="glyphicon glyphicon-envelope"></i> @Model.UserEmailAddress
</p>
</div>
</div>
</div>
</div>
这个标记显示了用户模型的三个属性。
保护个人资料页
在继续测试这个新功能之前,让我们确保只有授权用户可以访问用户资料页面。因此,打开 Program.cs文件,并将简介页配置为受保护的页面,如下所示。
// Program.cs
//...existing code...
// Add services to the container.
builder.Services.AddRazorPages(options =>
{
options.Conventions.AuthorizePage("/Catalog");
options.Conventions.AuthorizePage("/Account/Logout");
// 👇 new code
options.Conventions.AuthorizePage("/Account/Profile");
// 👆 new code
});
//...existing code...
你刚刚把个人资料页添加到需要授权的页面中。
链接个人资料页
最后,让我们使用户可以使用个人资料页。打开 _Layout.cshtml文件夹下的 Pages/Shared文件夹下的文件,更新其内容如下。
@* Pages/Shared/_Layout.cshtml *@
<!DOCTYPE html>
<html lang="en">
<!-- ...existing code -->
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
@if (User.Identity.IsAuthenticated)
{
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark display-6" asp-area=""
asp-page="/Catalog">Catalog</a>
</li>
</ul>
<ul class="navbar-nav ms-auto">
// 👇 new code
<li class="nav-item">
<a class="nav-link text-dark display-6"
asp-page="/Account/Profile">Hello @User.Identity.Name!</a>
</li>
// 👆 new code
<li class="nav-item">
<a class="nav-link text-dark display-6" asp-area=""
asp-page="/Account/Logout">Logout</a>
</li>
</ul>
} else {
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link text-dark display-6" asp-area=""
asp-page="/Account/Login">Login</a>
</li>
</ul>
}
</div>
<!-- ...existing code -->
你把链接添加到个人资料视图中的注销链接旁边。该链接将显示一个带有用户名的欢迎信息。而且,只有在用户通过认证后才会显示。
测试用户资料
回到你的浏览器,再次进行认证。作为第一个区别,你会注意到你又看到了同意屏幕。为什么?你之前没有接受吗?
实际上,这一次你的应用程序正在要求你提供一个新的信息:电子邮件。如果你将这个屏幕与之前的屏幕进行比较,你会注意到这个微妙的区别。

在你接受该屏幕后,你的新主页将看起来如下。

你可以看到注销链接旁边的欢迎词。如果你点击欢迎信息,用户资料将显示如下图所示。

概要
祝贺你!你完成了这个基本的旅程!你学会了如何通过Auth0在Razor Pages应用程序中集成认证。你已经看到了Auth0 ASP.NET Core Authentication SDK是如何在引擎盖下为你处理OpenID Connect的,从而避免你处理技术细节。你还学会了如何保护你的应用程序的私人页面以及如何实现注销。最后,你能够获得用户的数据来创建一个用户资料页面。
你可以在这个GitHub资源库中找到Razor Pages项目的完整代码。