Create a login mechanism using cookie authentication, and create a mechanism to be redirected if you are not authenticated

Page update date :
Page creation date :

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();

// === 省略 ===

AddAuthenticationAddCookie 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,ClaimClaimsIdentityClaimsPrincipal 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,ClaimClaimsIdentityClaimsPrincipal 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