Create a login mechanism using cookie authentication, and create a mechanism to be redirected if you are not authenticated
Operating environment
- Visual Studio
-
- Visual Studio Community 2022
- ASP.NET Core (MVC, Razor Pages)
- 6.0
At first
This time, ASP.NET Core will use cookie authentication as a login authentication mechanism. You can think of cookie authentication as being similar to traditional forms authentication.
Another authentication mechanism for ASP.NET Core is ASP.NET Core Identity. In addition to authentication using forms, this allows you to authenticate with APIs, use external login services, manage and reset passwords, etc. You can use a lot of features. However, from the viewpoint of just creating a simple login screen this time, it will be a somewhat exaggerated authentication mechanism. We won't use it this time.
In the cookie authentication tips introduced this time, you can not display anything other than the login screen unless you log in. If you try to navigate to another screen, you will be redirected to the login screen. If you log in, you can view other screens.
For the time being, you can log in by entering your user name and password on the login screen. The user authentication itself is implemented as a temporary place. In this case, the main focus is on the implementation of cookie authentication, so the essence of the determination process such as whether the password is correct is not the essence.
This tip describes only a portion of the program. For the complete code, download the complete program. It also covers both MVC and Razor Pages frameworks.
Create a project
Start Visual Studio and create a new project.
For Razor Pages, select ASP.NET Core Web App, or for MVC, select ASP.NET Core Web App (Model-View-Controller).
Specify a project name of your choice and a location for the project.
For the authentication type, select "None". If you choose other authentication, you will use ASP.NET Core Identity. When you are done with the settings, click the "Create" button.
After creating the project, the screen shown below will be displayed when debugging is executed. We will create a program based on this screen.
Edit Program.cs (Razor Pages, MVC Common)
Add the definitions required for cookie authentication to Program.cs. The namespaces to be added are as follows:
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
Here's builder.Services
the code to add to .
// === 省略 ===
// 「Razor Pages」のコード
// builder.Services.AddRazorPages();
// 「MVC」のコード
// builder.Services.AddControllersWithViews();
// ※ここから追加
// Cookie による認証スキームを追加する
builder.Services
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie();
builder.Services.AddAuthorization(options =>
{
// AllowAnonymous 属性が指定されていないすべての画面、アクションなどに対してユーザー認証が必要となる
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
// ※ここまで追加
var app = builder.Build();
// === 省略 ===
AddAuthentication
AddCookie
You can enable cookie authentication by executing the and method.
If you do not need to change the scheme name, CookieAuthenticationDefaults.AuthenticationScheme
specify .
AddAuthorization
If you options.FallbackPolicy
RequireAuthenticatedUser
specify for the method
You can apply an authentication-required policy to all pages, all controllers, and actions.
If you want to require authentication for anything other than the login screen, it is a useful method in terms of reducing the code and preventing errors in the description.
You will have to write code that does not require authentication for the login screen separately.
The following is app
the code for .
// === 省略 ===
app.UseRouting();
app.UseAuthentication(); // [追加] 認証
app.UseAuthorization(); // 認可
// 「Razor Pages」のコード
// app.MapRazorPages();
// 「MVC」のコード
// app.MapControllerRoute(
// name: "default",
// pattern: "{controller=Home}/{action=Index}/{id?}");
// === 省略 ===
Since we want to add authentication functionality to the application, app.UseAuthentication()
we will add .
The description location is according to the MSDN documentation, app. UseAuthorization().
Other than that, it's still a template.
Programs for Razor Pages projects
Create a login page (Pages/Account/Login.cshtml.cs)
Create a file
Create a login page. The file path should be created as "/Pages/Account/Login.cshtml".
This is because the default login path is like that.
If you want to change this path, you Program.cs
can do so by setting the argument of the AddCookie
method of .
You can create it by copying other files instead of creating it from the menu, but in that case, please fix the program correctly.
Allow users to access the login page without logging in
Program.cs
Since all pages can only be accessed when you are logged in, you need to set only the login page so that you can access it even if you are not logged in.
AllowAnonymous
By adding attributes, the target page can be accessed even if it is not authenticated.
AllowAnonymous
Attributes may be used in other places other than the login screen, such as API operations unrelated to cookie authentication.
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Security.Claims;
namespace AspNetCoreCookieAuthenticationRazorPages.Pages.Account
{
[AllowAnonymous]
public class LoginModel : PageModel
{
}
}
Create a variable to receive input
When you log in, you declare your username to be able to receive those values because you enter your password.
// 省略
[AllowAnonymous]
public class LoginModel : PageModel
{
<summary>ユーザー名。</summary>
[BindProperty]
[Required]
[DisplayName("ユーザー名")]
public string UserName { get; set; } = "";
<summary>パスワード。</summary>
[BindProperty]
[Required]
[DataType(DataType.Password)]
[DisplayName("パスワード")]
public string Password { get; set; } = "";
}
Define a username and password for login authentication
Originally, it would be stored in a database, etc., but since user judgment is not the main focus this time, I will make it as a temporary place.
// 省略
[AllowAnonymous]
public class LoginModel : PageModel
{
// 省略
<summary>仮のユーザーデータベースとする。</summary>
private Dictionary<string, string> UserAccounts { get; set; } = new Dictionary<string, string>
{
{ "user1", "password1" },
{ "user2", "password2" },
};
}
Login process
<summary>ログイン処理。</summary>
public async Task<ActionResult> OnPost()
{
// 入力内容にエラーがある場合は処理を中断してエラー表示
if (ModelState.IsValid == false) return Page();
// ユーザーの存在チェックとパスワードチェック (仮実装)
// 本 Tips は Cookie 認証ができるかどうかの確認であるため入力内容やパスワードの厳密なチェックは行っていません
if (UserAccounts.TryGetValue(UserName, out string? getPass) == false || Password != getPass)
{
ModelState.AddModelError("", "ユーザー名またはパスワードが一致しません。");
return Page();
}
// サインインに必要なプリンシパルを作る
var claims = new[] { new Claim(ClaimTypes.Name, UserName) };
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var principal = new ClaimsPrincipal(identity);
// 認証クッキーをレスポンスに追加
await HttpContext.SignInAsync(principal);
// ログインが必要な画面にリダイレクトします
return RedirectToPage("/Index");
}
This is the authentication process after pressing the login button. If the user name and password match, authentication is possible.
The code described is the minimum code required for authentication,Claim
ClaimsIdentity
ClaimsPrincipal
and
HttpContext.SignInAsync
By calling the method, a cookie is generated and authenticated.
If additional claims are required or cookie expiration is required, additional parameters are added.
After logging in, you are redirected to , where /Index
authentication is required.
Logout process
<summary>ログアウト処理。</summary>
public async Task OnGetLogout()
{
// 認証クッキーをレスポンスから削除
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}
Logout
When accessed with a handler, it is processed to log out.
HttpContext.SignOutAsync
By calling the method, you can delete the cookie and return it to a state where you are not logged in.
Creating a View
We don't take into account the appearance. Add fields for entering your username and password as shown below, and place a button to log in.
You should also have a link to access without logging in for /Index
testing.
Since it is troublesome to enter the user name and password, the initial value is set.
@page
@model AspNetCoreCookieAuthenticationRazorPages.Pages.Account.LoginModel
@{}
<form asp-action="Login">
<div class="row m-1 g-3">
<div class="col-sm-6 offset-sm-3">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
</div>
</div>
<div class="row m-1 g-3">
<div class="col-sm-6 offset-sm-3">
<label asp-for="UserName" class="form-label"></label>
<input asp-for="UserName" class="form-control" value="user1" />
<span asp-validation-for="UserName" class="text-danger"></span>
</div>
</div>
<div class="row m-1 g-3">
<div class="col-sm-6 offset-sm-3">
<label asp-for="Password" class="form-label"></label>
<input asp-for="Password" class="form-control" value="password1" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
</div>
<div class="row m-1 g-3">
<div class="col-sm-6 offset-sm-3">
<button type="submit" class="btn btn-primary">ログイン</button>
</div>
</div>
<div class="row m-1 g-3">
<div class="col-sm-6 offset-sm-3">
<a asp-page="/Index">認証が必要な画面へ直接リンク</a>
</div>
</div>
</form>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}
Create a logout link (/Pages/Shared/_Layout.cshtml)
We won't make any changes to the home screen, but we'll put a logout link in the navigation bar. Also, for testing, post a link that takes you to the login screen without logging out.
<!-- 中略 -->
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>
</li>
<!-- ここから追加 -->
<li class="nav-item">
<a class="nav-link text-dark" asp-page="/Account/Login" asp-page-handler="Logout">ログアウト</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-page="/Account/Login">ログアウトせずログインへ</a>
</li>
<!-- ここまで追加 -->
</ul>
</div>
<!-- 中略 -->
That's it for the Razor Pages code.
Programs for MVC Projects
Creating a Login Model
You have created a model to receive the values entered on the login screen.
namespace AspNetCoreCookieAuthenticationMvc.Models
{
public class LoginModel
{
<summary>ユーザー名。</summary>
[Required]
[DisplayName("ユーザー名")]
public string UserName { get; set; } = "";
<summary>パスワード。</summary>
[Required]
[DataType(DataType.Password)]
[DisplayName("パスワード")]
public string Password { get; set; } = "";
}
}
Creating an AccountController
Create the controllers and actions required to create the login screen.
Create the controller name AccountController
as .
This is because by default, the controller name and action name on the login screen are set to "~/Account/Login".
If you want to change this path, you can do so in the options of AddCookie
the method in Program.cs.
For now, we will proceed with the default settings.
First, let's create the Controller side.
using AspNetCoreCookieAuthenticationMvc.Models;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
namespace AspNetCoreCookieAuthenticationMvc.Controllers
{
<remarks>
<see cref="AllowAnonymous"/> 属性は Cookie 認証していなくてもアクセスできる Action (Controller) であることを示す。
</remarks>
[AllowAnonymous]
public class AccountController : Controller
{
}
}
AllowAnonymous
By granting the attribute, all actions in it can be performed without being authenticated.
This allows you to access only the login screen without authentication.
AllowAnonymous
Attributes may also be used in API-only controllers that are not related to cookie authentication in other places other than the login screen.
Next, define the users and passwords that can log in. Originally, it would be stored in a database, etc., but since user judgment is not the main focus this time, I will make it as a temporary place.
[AllowAnonymous]
public class AccountController : Controller
{
<summary>仮のユーザーデータベースとする。</summary>
private Dictionary<string, string> UserAccounts { get; set; } = new Dictionary<string, string>
{
{ "user1", "password1" },
{ "user2", "password2" },
};
}
The following is an action that displays the login screen. Since it is only displayed, it returns the view as is.
<summary>ログイン画面を表示します。</summary>
public IActionResult Login() => View();
Below is the code to be processed when logging in.
<summary>ログイン処理を実行します。</summary>
[HttpPost]
public async Task<IActionResult> Login(LoginModel model)
{
// 入力内容にエラーがある場合は処理を中断してエラー表示
if (ModelState.IsValid == false) return View(model);
// ユーザーの存在チェックとパスワードチェック (仮実装)
// 本 Tips は Cookie 認証ができるかどうかの確認であるため入力内容やパスワードの厳密なチェックは行っていません
if (UserAccounts.TryGetValue(model.UserName, out string? getPass) == false || model.Password != getPass)
{
ModelState.AddModelError("", "ユーザー名またはパスワードが一致しません。");
return View(model);
}
// サインインに必要なプリンシパルを作る
var claims = new[] { new Claim(ClaimTypes.Name, model.UserName) };
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var principal = new ClaimsPrincipal(identity);
// 認証クッキーをレスポンスに追加
await HttpContext.SignInAsync(principal);
// ログインが必要な画面にリダイレクトします
return RedirectToAction(nameof(HomeController.Index), "Home");
}
This is the authentication process after pressing the login button. If the user name and password match, authentication is possible.
The code described is the minimum code required for authentication,Claim
ClaimsIdentity
ClaimsPrincipal
and
HttpContext.SignInAsync
By calling the method, a cookie is generated and authenticated.
If additional claims are required or cookie expiration is required, additional parameters are added.
After logging in, you are redirected to , where ~/Home/Index
authentication is required.
Finally, the logout process is added.
<summary>ログアウト処理を実行します。</summary>
public async Task<IActionResult> Logout()
{
// 認証クッキーをレスポンスから削除
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
// ログイン画面にリダイレクト
return RedirectToAction(nameof(Login));
}
HttpContext.SignOutAsync
By calling the method, you can delete the cookie and return it to a state where you are not logged in.
Create a view (login form) (/Views/Account/Login.cshtml)
Login
Right-click the action to add a view. You can also create it by copying it from another file.
We don't take into account the appearance. Add fields for entering your username and password as shown below, and place a button to log in.
You should also have a link to access without logging in for Home/Index
testing.
Since it is troublesome to enter the user name and password, the initial value is set.
@model LoginModel
@{}
<form asp-action="Login">
<div class="row m-1 g-3">
<div class="col-sm-6 offset-sm-3">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
</div>
</div>
<div class="row m-1 g-3">
<div class="col-sm-6 offset-sm-3">
<label asp-for="UserName" class="form-label"></label>
<input asp-for="UserName" class="form-control" value="user1" />
<span asp-validation-for="UserName" class="text-danger"></span>
</div>
</div>
<div class="row m-1 g-3">
<div class="col-sm-6 offset-sm-3">
<label asp-for="Password" class="form-label"></label>
<input asp-for="Password" class="form-control" value="password1" />
<span asp-validation-for="Password" class="text-danger"></span>
</div>
</div>
<div class="row m-1 g-3">
<div class="col-sm-6 offset-sm-3">
<button type="submit" class="btn btn-primary">ログイン</button>
</div>
</div>
<div class="row m-1 g-3">
<div class="col-sm-6 offset-sm-3">
<a asp-controller="Home" asp-action="Index">認証が必要な画面へ直接リンク</a>
</div>
</div>
</form>
@section Scripts {
@{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}
Create a logout link (/Views/Shared/_Layout.cshtml)
We won't make any changes to the home screen, but we'll put a logout link in the navigation bar. Also, for testing, post a link that takes you to the login screen without logging out.
<!-- 中略 -->
<div class="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</li>
<!-- ここから追加 -->
<li class="nav-item">
<a class="nav-link text-dark" asp-controller="Account" asp-action="Logout">ログアウト</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-controller="Account" asp-action="Login">ログアウトせずログインへ</a>
</li>
<!-- ここまで追加 -->
</ul>
</div>
<!-- 中略 -->
That's it for the MVC code.
Confirmation of operation
This completes the minimum required implementation of cookie authentication. Try running it and see how it works. The behavior should change depending on whether you are logged in or not. As a simple example, you can see the following behavior.
Operation | ⇒ | operation result |
---|---|---|
Go home without logging in | ⇒ | Redirect to login screen |
login | ⇒ | Go to the home screen |
Log out of home and go home without logging in | ⇒ | Redirect to login screen |
Go home without logging out of the home and logging in | ⇒ | Go to the home screen |