MS 문서, 특히 특정 프레임워크에 관한 문서는 기본적으로 배경 지식이 있음을 전제합니다. (Get started 항목은 빼고요. 꼬시는 문서라서, 마치 모든 게 쉬운 것처럼 보이도록 작성되어 있습니다)
특히 asp.net core 에 대한 문서가 기본으로 전제하는 웹 지식은 너무 방대해서, 기본기가 없으면 문서를 읽어도 무슨 말인지 모를 때가 많습니다.
브라우저가 세션 쿠키를 자동으로 지우는 시점은 브라우저 마다 다를 수 있는데, 보통 브라우저 윈도우가 닫힐 때 지웁니다.
-
브라우저 윈도우에 여러 개의 탭 중 하나가 서버에 접속한 경우
- 탭 닫음 : 세션 쿠키 유지
- 윈도우 닫음 : 세션 쿠키 삭제.
-
브라우저 윈도우에 하나의 탭이 있고, 그 탭이 서버에 접속한 경우
- 탭 닫음 => 윈도우 닫음 : 세션 쿠키 삭제
- 윈도우 닫음 : 세션 쿠키 삭제
그리고 중요한 점은, "브라우저의 (탭) 닫힘"은 어떤 식으로든 서버에 통지되지 않는다는 점입니다.
닫힘은 클라이언트 코드, 예를 들면 javascript 나 블레이저 웹어셈블리 코드에는 통지될 수 있습니다.
// javascript
window.addEventListener('beforeunload', function(event) {
event.returnValue = 'You have unsaved changes!';
});
이론적으로는 클라이언트 코드가 이러한 통지를 받을 때 세션 쿠키를 지울 수는 있는데, 문제는 asp.net core 가 설정하는 기본 쿠키는 httponly 로 설정되기 때문에, 클라이언트 코드가 임의로 접근할 수 없다는 점입니다.
대신, 우회적으로 Logout url 로 이동(Navigation)하는 방법을 사용할 수 있습니다.
(브라우저 닫힘 이벤트 핸들러에서, Logout url 에 요청을 보내, 서버의 응답에 의해 세션 쿠키를 지우게 하는 것입니다.)
참고로, 블레이저 8.0 템플릿 프로젝트를 사용했다면, Logout url 을 페이지 요소가 아니라, minimal api controller 가 처리하도록 구현되어 있습니다.
// program.cs
// ...
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(typeof(BlazorApp1.Client._Imports).Assembly);
// Add additional endpoints required by the Identity /Account Razor components.
app.MapAdditionalIdentityEndpoints();
app.Run();
마지막 미들웨어 확장 메서드 MapAdditionalIdentityEndpoints의 정의를 보면, 내부에서 SignOut 한다는 것을 알 수 있습니다.
namespace Microsoft.AspNetCore.Routing;
internal static class IdentityComponentsEndpointRouteBuilderExtensions
{
// ...
public static IEndpointConventionBuilder MapAdditionalIdentityEndpoints(this IEndpointRouteBuilder endpoints)
{
ArgumentNullException.ThrowIfNull(endpoints);
var accountGroup = endpoints.MapGroup("/Account");
// ...
accountGroup.MapPost("/Logout", async (
ClaimsPrincipal user,
SignInManager<ApplicationUser> signInManager,
[FromForm] string returnUrl) =>
{
await signInManager.SignOutAsync();
return TypedResults.LocalRedirect($"~/{returnUrl}");
});
// ...
보시다시피 폼 데이터를 요구하는데, 이는 NavMenu 요소에 구현되어 있습니다.
// NavMenu.razor
// ...
<AuthorizeView>
<Authorized>
// ...
<div class="nav-item px-3">
<form action="Account/Logout" method="post">
<AntiforgeryToken />
<input type="hidden" name="ReturnUrl" value="@currentUrl" />
<button type="submit" class="nav-link">
<span class="bi bi-arrow-bar-left-nav-menu" aria-hidden="true"></span> Logout
</button>
</form>
</div>
보시다시피, 생 html 로 작성되어 있어, 블레이저와 상관 없도록 구현되어 있습니다.
(AntiforgeryToken 은 Asp.Net core 가 제공합니다)
이렇게 구현된 이유가 있습니다.
아마 블레이저의 인증과 관련된 글들을 보셨으면, 인증이 약간 복잡하게 설명되어 있음을 알 수 있습니다. 이는 블레이저 세션의 특성 때문에 기인한 것으로,
< http session >
- 클라이언트: 요청 => 서버.
- 서버 : 응답, 블레이저 자원(blazor.web.js)이 포함됨.
- 클라이언트의 blazor.web.js 실행
</ http session >
< 블레이저 세션 >
Static SSR 인 경우: Enhance navigation, Enhance form
서버 인터렉티브 모드인 경우 : 웹소켓 시작
< 웹소켓 세션 >
바이너리 데이터 교환
</ 웹소켓 세션 >
</ 블레이저 세션 >
쿠키는 Http 세션에서만 유의미하고, 블레이저 세션과는 직접적 상관이 없습니다.
그래서, 세션 쿠키를 지우기 위한 Logout 은 form 을 통해 (새로운) http 세션을 시작하는 것입니다.
그러나, 인증과 관련한 행위들을 블레이저 세션에서도 가능하게 하는 장치들이 있어, 페이지 요소에서도 로그아웃을 처리할 수 있습니다. (블레이저 인증 문서를 참고하시길)