ASP.NET SPAサイトにcookieベースのログイン認証を実装する
ASP.NET Core
のSPAプロジェクトにcookie(session)ベースのログイン認証を実装する方法を紹介します。
SPAはページはすべてJavaScript
で実装されているため、ページの情報を保護するのは難しいです。(routerなどで保護してもJavaScript
のソースから見えてしまうため)
そのため、ページにはデザインなどの枠組みだけ実装しておき、機密情報はajaxでデータとして取得するようにします。
ページを保護するのではなく、ajaxでアクセスされた際のデータを保護するように認証処理を実装します。
Startup.csの修正
Startup.cs
に設定を追記します。
上述したように、ページアクセスに対しての保護は行わないため、未認証時にログイン画面にリダイレクトする処理は行いません。
そのため、未認証時はログイン画面に遷移するのではなくエラーを返すようにします。
(ログイン画面に遷移する処理はクライアント側で行います)
認証保護はコントローラーでURLごとに設定する方法もありますが、今回はアプリケーション全体で認証保護の設定を行うようにします。
ただし、静的ファイル(html, js, css)は認証保護の影響は受けません。
そのためSPAの初回アクセス時などは問題なくhtmlがレスポンスされます。
Startup.cs(一部抜粋)
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.AspNetCore.Authorization;
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
// cookieベースの認証
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddCookie(options =>
{
options.SlidingExpiration = true;
options.Events.OnRedirectToLogin = cxt =>
{
// ログイン画面に遷移するのではなくエラーを返す
cxt.Response.StatusCode = StatusCodes.Status401Unauthorized;
return Task.CompletedTask;
};
options.Events.OnRedirectToAccessDenied = cxt =>
{
// エラー画面に遷移するのではなくエラーを返す
cxt.Response.StatusCode = StatusCodes.Status403Forbidden;
return Task.CompletedTask;
};
options.Events.OnRedirectToLogout = cxt => Task.CompletedTask;
});
services.AddControllersWithViews(options =>
{
// すべてのアクセスに対して認証保護を適用する
options.Filters.Add(new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build()));
});
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IAntiforgery antiforgery)
{
app.UseRouting();
// app.UseRouting();の下に追加
app.UseAuthentication();
app.UseAuthorization();
動作確認
プロジェクトを作成した際の雛形として作成されたWeatherForecastController
で動作確認を行います。
ここではAPI
としてajax
でアクセスするため、Route
の設定箇所に以下のようにapi
を追加します。
こうすると、api/WeatherForecast
でアクセスできるようになります。
[ApiController]
[Route("[controller]")]
[Route("api/[controller]")]
public class WeatherForecastController : ControllerBase
クライアント側で、ajaxのリクエストを投げます。
ここではajax
のライブラリにaxios
を使用します。
const response = axios.get('/api/WeatherForecast');
未認証の状態でアクセスをすると401(Unauthorized)のエラーになることが確認できます。
認証保護の除外
例えばログイン時のAPIなど、未認証の状態でもアクセスしたい場合があります。
その場合は対象のメソッドにAllowAnonymous
を設定します。
using Microsoft.AspNetCore.Authorization;
[HttpGet]
[AllowAnonymous]
public IEnumerable<WeatherForecast> Get()
AllowAnonymous
を設定後にアクセスするとデータが取得できることを確認できます。
ログイン/ログアウト処理
認証用のコントローラーを作成し、以下のようにログイン処理とログアウト処理を実装します。
「認証判定処理」の部分は実際にはデータベースなどでIDやパスワードを使って判定することになると思います。
AuthController.cs
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
namespace code.Controllers
{
[ApiController]
[Route("api/[controller]/[action]")]
public class AuthController : ControllerBase
{
public class LoginRequest
{
public string login_id { get; set; }
public string password { get; set; }
}
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Login(LoginRequest request)
{
if (認証判定処理(request.login_id, request.password) == false)
{
return BadRequest("ユーザー名またはパスワードが違います。");
}
var claims = new List<Claim>
{
new Claim(ClaimTypes.NameIdentifier, user.login_id),
};
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var claimsPrincipal = new ClaimsPrincipal(identity);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal);
return Ok();
}
[HttpPost]
public async Task<IActionResult> Logout()
{
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return Ok();
}
}
}
クライアント側からは以下のようにログイン処理をコールします。
const response = await axios.post('/api/auth/Login', { login_id: 入力したログインID, password: 入力したパスワード });
ログイン処理に成功した場合、.AspNetCore.Cookies
という名前で認証cookieがレスポンスされます。
次回以降のリクエストにはこのcookieが自動的に含まれるので、ログイン状態を維持できます。
また、ログアウト処理をした場合、.AspNetCore.Cookies
のcookieが削除されることが確認できます。