Blazor에서의 Identity 서비스

Blazor에는 4가지 렌더모드가 있습니다.

  1. SSR
  2. Interactive SSR
  3. Interactive WASM
  4. Auto
    여기에 스트림렌더링, 프리렌더링 조합이 있다고 하구요.

이제 겨우 몇개의 강좌들을 따라해 보고 C#도 모르는 상태에서 겨우 이제 흐름 정도만 익혔습니다.

그런데 지금 가장 혼란을 겪고 있는 주제가 바로 로그인입니다.

예를들어,

프로젝트 생성시에 Server(InterActive) + 개별인증으로 생성하였습니다.
이것으로 update-database하고 프로젝트 디버깅을 해서

  1. 계정 생성
  2. 로그인
    잘됩니다.

이후 브라우저를 닫고 새로 브라우저를 열면, 토큰이 계속 살아 있어서 새로 로그인하지 않아도 첫 화면이 로그이된 상태입니다.
당연 'remember me’를 선택하지도 않았었는데 말이죠.

즉, 브라우저나 탭을 닫으면 쿠키도 자동 삭제되는 것이 '세션쿠키’라고 불리우는 것 같은데,
저는 rememberme를 선택하지도 않았는데 왜 쿠키가 계속 살아 있는거죠?(persistant)

관련 키워드를 찾아봐도, MSDN을 찾아봐도 state 관리 상태에 대해서는 나오는데 이 부분에 관한 자료를 검색할 수가 없습니다.

  1. 어떤 글은 circuit 클래스를 사용해야 탭닫을 때 쿠키삭제할 수 있다고 하기도 하고
  2. blazor 자체 특성상 클라이언트의 브라우저가 닫혔는지 알 방법이 없기 때문에 (각 렌더링 모드별로 약간씩 다르긴 해도), 그래서 javascript로 강제 로그아웃 페이지 호출해야한다.
  3. 거기다가 또 WASM 때문에 api에 대한 jwt 토큰 설명등 다 짬뽕이 되어서
    제가 원하는 현재 Interactive SSR에서의 세션쿠키에 대한 내용을 도저히 정리할 수가 없습니다.(파악할 수가 없습니다.)

즉, login시에 생성되는 쿠키과 관련해서 왜 세션쿠키에 대한 설정이나 설명의 글을 찾을 수가 없는지 좀 여쭤보려고 합니다.
program.cs에서 인증관련 세팅에서 무언가 할 수 있어야할 것 같은데, 또 remeber me의 boolean에서 이값에 따라 세션인지, 아님 persistent인지 분기하는 코드도 보여야할 것 같은데 내용을 찾을 수가 없어 너무 어리둥절 합니다.

하고 싶은 것은, 브라우저를 닫거나 탭을 닫으면 왜 쿠키가 자동 삭제되지 않느냐가 질문의 첫 출발이었습니다.

MS 문서, 특히 특정 프레임워크에 관한 문서는 기본적으로 배경 지식이 있음을 전제합니다. (Get started 항목은 빼고요. 꼬시는 문서라서, 마치 모든 게 쉬운 것처럼 보이도록 작성되어 있습니다)

특히 asp.net core 에 대한 문서가 기본으로 전제하는 웹 지식은 너무 방대해서, 기본기가 없으면 문서를 읽어도 무슨 말인지 모를 때가 많습니다.

브라우저가 세션 쿠키를 자동으로 지우는 시점은 브라우저 마다 다를 수 있는데, 보통 브라우저 윈도우가 닫힐 때 지웁니다.

  1. 브라우저 윈도우에 여러 개의 탭 중 하나가 서버에 접속한 경우

    1. 탭 닫음 : 세션 쿠키 유지
    2. 윈도우 닫음 : 세션 쿠키 삭제.
  2. 브라우저 윈도우에 하나의 탭이 있고, 그 탭이 서버에 접속한 경우

    1. 탭 닫음 => 윈도우 닫음 : 세션 쿠키 삭제
    2. 윈도우 닫음 : 세션 쿠키 삭제

그리고 중요한 점은, "브라우저의 (탭) 닫힘"은 어떤 식으로든 서버에 통지되지 않는다는 점입니다.

닫힘은 클라이언트 코드, 예를 들면 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 >

  1. 클라이언트: 요청 => 서버.
  2. 서버 : 응답, 블레이저 자원(blazor.web.js)이 포함됨.
  3. 클라이언트의 blazor.web.js 실행

</ http session >
< 블레이저 세션 >
Static SSR 인 경우: Enhance navigation, Enhance form
서버 인터렉티브 모드인 경우 : 웹소켓 시작
< 웹소켓 세션 >
바이너리 데이터 교환
</ 웹소켓 세션 >
</ 블레이저 세션 >

쿠키는 Http 세션에서만 유의미하고, 블레이저 세션과는 직접적 상관이 없습니다.
그래서, 세션 쿠키를 지우기 위한 Logout 은 form 을 통해 (새로운) http 세션을 시작하는 것입니다.

그러나, 인증과 관련한 행위들을 블레이저 세션에서도 가능하게 하는 장치들이 있어, 페이지 요소에서도 로그아웃을 처리할 수 있습니다. (블레이저 인증 문서를 참고하시길)

2 Likes

SSR 로 동작할때(none, server: 로케이션 per page/component, WASM: 로케이션 per/component)
는 브라우저 쿠키로 identity가 관리되며 blazor.web.js로 fetch api를 통해 통신합니다. 이땐 브라우저 쿠키가 삭제되면 당연히도 로그아웃됩니다.

SSR이 아닌경우(로케이션 global) 브라우저 쿠키는 브리우저에 저장은 되지만 identity가 곧바로 SignalR(웹 소켓)로 이관되고 통신합니다. 그래서 더이상 브라우저 쿠키와는 상관이 없어집니다.

쿠키 삭제해도 여전히 SignalR에 identity 가 있죠. 웹브라우저를 닫고(쿠키삭제) 되고나면 다시 열어서 ‘강력 새로고침’하면 로그아웃될겁니다.

친절하신 답변 너무 감사합니다. 더듬거리며 따라가면서 로그아웃 From으로 호출해보았다던지 했던 것들이 하나의 흐름으로 이제 머리속에 잡혀집니다.

빅스퀘어님이 주신 댓글 여러번 읽어보았으나 그러나 여전한 질문은,
왜 디버깅했을 때

  1. 이미 별도의 크롬 다른 탭들이 열려 있는 브라우저 1
  2. 새롭게 디버깅 탭으로 열리는 브라우저 2
  • 로그인
  1. 브라우저 2 닫음
    이후 다시 디버깅 누르고 브라우저 3이 열리는데도 로그인 정보가 그대로 살아 있어서 이미 로그인 되어 있는 상태

이 질문은 해결이 되지 않습니다.

제 프로젝트를 VS2022이 있는 동일 컴퓨터에 SQLEXPRESS, IIS 올리고 테스트를 해보았는데 디버깅시에 로그인 정보가 살아 있는 그대로 똑같은 상황입니다.(즉, 디버깅 모드에서 특이점인가 하고 해보았습니다.)

가까이 계시다면 커피 한잔 대접해드리고 싶습니다. 댓글 감사하고 더 찾아보아야겠습니다.

1 Like

// csharp
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddIdentityCookies();

builder.Services.ConfigureApplicationCookie(options =>
{
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.SameSite = SameSiteMode.Strict;
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
options.SlidingExpiration = true;
options.Cookie.MaxAge = null; // Ensure it is a session cookie
});

세션쿠키 설정을 별도로 해줘야하나 해서 추가해 보았습니다.


세션 쿠키로 브라우저에 나오는데도 왜 탭을 닫거나 브라우저를 닫아도 계속 로그인 정보가 살아 있는지 파악을 할 수가 없네요.

먼저 답변 감사합니다.

여러 설명 내용이 더 혼란스럽게 느껴집니다. SingalR이 Identity(사용자, 인증, 권한 정보)를 가지고 있지 않지 않나요? 그냥 통신웹소켓 정도로만 이해하고 있었고…또 그게 로케이션별로 달라지나요?
(아직 뭐 이런 문제에 직접 제가 필요한 과정은 없었던지라…)
전 그냥 Interactive Server Rendermode가 걸린 페이지에서 SignalR이 작동된다. 이 여부는 개발모드에서 네트워크에서 확인해 오던 수준이라…

그리고 일반유저가 보통 브라우저 열때마다 강력 새로고침을 하지는 않잖아요.
그냥 대부분은 브라우저 닫고 자리를 뜨니까, 이걸 기본 보안(로그인) 세팅으로 하고 싶은것이거든요.
브라우저가 닫히거나 탭이 닫힐 때, javascript 호출 없이 왜 세션쿠기가 자동 삭제가 안되느냐가 지금 제가 찾고 있는 문제있습니다.

강력 새로고침얘기는 메카니즘에 관한 얘기입니다.

위에 말했듯 글로벌 로케이션에서는 소켓연결이 된뒤로는 쿠키 identity를 안쓰기 때문에 SignalR 이 끊어질때(브라우저가 닫힐때) 서버에서 해당 유저를 로그아웃 처리해주면됩니다. 그에대한 핸들러가 … onDisconnected였나 써킷핸들러였나 기억이 안나네요

  1. 글로벌 로케이션과 SignalR, Identity 쿠키를 쓰지 않는다는 말씀이 이해가 너무 안됩니다.
    글로벌 로케이션이라고 하면 프로젝트 전체에 전역으로 해당 렌더링 모드를 강제할 것인지 아닌지만 체크하는 것으로 알고 있는지라…
  2. overstack을 보면 누가 써킷핸들러로 브라우저 닫혔을 때 처리해라고 조언했던데, github에 있는 예제도 너무 오래되고, 의문이 드는게, 그럴거면 그냥 js로 검출해서 logout from 호출해버리면 되는데…굳이…

그리고 자료들을 검색하다가 의문이 드는게,

Blazor 서버에서 아무리 클라이언트의 상태를 알 수 없다 하더라도, 클라이언트에 세션 쿠키가 생성됐다면 그와는 별개로 클라이언트 브라우저가 닫히면(또는 탭이 닫히면) 세션 쿠키는 날라가야 하는데…이게 왜 살아 있냐는 것에 대한 답을 못찾고 있습니다.

위에 올린 이미지에서도 세션쿠키로 로컬(클라이언트)에 있는데 저게 왜 안 사라지는지…이건 어떻게보면 Blazor 차원의 문제가 아닌거 같고…

혹시 크롬 사용하시나요?
설정 - 시작 그룹의 설정이 다음처럼 되어 있으면 브라우저 종료해도 세션 쿠키도 삭제하지 않더라구요

크롬, 엣지 같은 현상입니다.

Blazor 쿠기 인증 사용(기본값) 시에 'remember me’를 체크하지 않으면 토큰이 세션쿠키로 생성되어야 하고,
개발모드에서도 보면 분명 세션쿠키임이 확인되는데, 이상하네요.

해외 포럼에도 관련 글은 없고, 오직 찾아지는 것은 Circuit 클래스로 접속자 파악해서 이벤트 발생하면 logout form을 콜아웃해라고 하는데…

마소가 이 정도로 일하지 않았을것 같고…그렇다면 결국 나의 무지의 한계라고 볼 수 밖에 없는 것인지…

정확히는 “쿠키가 왜 삭제되지 않았는지?” 알 수 없다… 겠지요.

지금 가지고 있는 의문은 본인 생각에 세션이 끝나야 할 것 같은데, 브라우저가 왜 안 끝내지? 하는 것과 같습니다. 일종의 아노미 현상이라고 볼 수 있습니다.

서버가 할 수 있는 일은 쿠키를 응답 해더에 보내고, 요청에 포함된 쿠키가 "인증 티켓"이라면, 인증에 사용할 뿐입니다.

세션의 종료 시점을 결정하는 것은 그 브라우저 맘입니다.
어떤 브라우저는 탭이 닫힐 때 일수도, 혹은 윈도우가 닫힐 때 일 수도, 혹은 모든 윈도우가 닫힐 때일 수도 있습니다.

브라우저가 어떤 식으로 결정하든, 세션이 종료되었다고 판단하면 세션 쿠키는 사라집니다.

Remember me 는 쿠키를 세션과 상관 없게 만들 뿐입니다.
쿠키의 유효기간이 종료되거나, 서버가 명백하게 지우라고 요청하지 않는 한, 브라우저는 쿠키를 삭제하지 않습니다.

만약, 모든 브라우저를 닫고 나서야 세션 쿠기가 삭제된다면,

크롬/엣지는 “모든 브라우저를 닫아야 세션이 종료되는 구나”

라고 추측할 뿐이지, 왜 그때서야 세션이 종료되냐고 시비를 걸 일은 아니라는 의미입니다.

크롬/엣지의 세션 종료 시점이 "모든 브라우저를 닫을 때"임을 알았다면,

Remember me 를 클릭하고, 모든 브라우저를 닫아 세션을 종료시킨 후, 쿠키 유효기간이 만료되기 이전에 다시 브라우저를 열어 접속해보시면, 다시 로그인 되어 있을 것입니다. 이는 쿠키가 세션을 넘어서 살아 남음을 의미합니다.

브라우저가 세션 쿠키를 다루는 방식이 맘에 안 든다면, 쿠키를 통한 인증을 하지 않으면 됩니다.

이 경우, 클라이언트 코드와 서버 코드를 분리시키고, 클라이언트 코드가 토큰의 관리를 책임져야겠죠. 이렇게 하면, 인증 티켓의 삭제 시점을 얼마든지 정밀하게 제어할 수 있습니다.

  1. Identity Token은 쿠키가 기본값이고
  2. 그리고 별도의 설정이 없어도 세션쿠키로 생성된다고 읽었습니다.

세션쿠키는 세션에만 살아있기 때문에 탭이 닫히거나 브라우저가 닫히면 자동 삭제되어야 하는것이 아닌가요? 어느 브라우저나 그게 기본 아닌가요?

Persistent 쿠키로 설정(AddAuthentication 설정이나 Persistent - remember me가 True)되면 지정된 시간까지 이 발급된 토큰은 유효한 것이구요.

Blazor는 렌더링 방식이야 어떠하든 약간씩 다르지만, 이걸 State를 유지하기 위해서 AuthenticationProvider가 존재하고
또, 내부적으로는 minimal api로 인증을 하고 있는데…

말이 약간 셋지만,

제가 브라우저 작동 방식이 마음에 안들어 하는건 어림도 없는 소리이고, 다만 저는 기본작동방식
(예를 들면, 어느 사이트든 탭을 닫아버리거나 브라우저 닫아버리면 다음에 접근시에 무조건 로그인들 하고 있잖습니까?)

  1. 디폴트가 왜 Blazor에서는 분명 세션쿠키가 생성되었다고 하는데 탭과 브라우저를 닫아도 삭제(또는 expired)가 안되고 있냐는 것이 질문입니다.
  2. MS에서 무조건 persistent 쿠키로만 생성된다 문서에서 말하고 있으면 당연히 아 그렇구나 하고 그 이외에 내용을 찾아볼텐데,
  3. 문서에서 세션쿠키라고 했는데, 그럼 세션쿠키일지라도 탭을 닫아도 그게 정상이야. Blazor는 기존언어와 달라. 이렇게 설명되어 있다거나, 그 후속 내용들이 이어져 있음 저도 이런 질문 할 이유도 없죠. 거기서 끝이란 것이 제가 지금까지 찾아본 전부라서 답답한 것입니다.

예를들어 게임방이나 공용PC에서 제 WebAPP을 접근하고 로그인했는데 깜빡하고 브라우저만 닫고 자리를 뜨면(그리고 다들 일반적으로 이렇게 웹서비스를 이용하잖습니까?), 이야 말로 보안에 중대한 문제이잖습니까?

이런 경우 쿠키 생존 시간을 설정하라…뭐 이런 글들은 보입니다만, 으잉? 뭔소리지? 이렇습니다. 허다한 기존 웹서비스들은 탭과 브라우저 닫으면 로그인해제(세션쿠키삭제)가 되고 있잖아? 이런…

어느 언어가 이렇게 작동하는걸 기본이라고…MS가 Identity 서비스를 이렇게 만들었을리가 없을텐데…또는 이게 기본값이라고 하면 이런 경우 안내가 되어 있어야할텐데…제 현재 기준에서는 이게 뭐지? 왜 내용이 끊겨버렸지?

그래서 제가 잘못알고 있는게 있다면 어디를 찾아 이어나가야할지 질문드린 것이었습니다.

릴리스 모드로 실행해보세요

이미 해보았습니다. 동일합니다. 제가 유투브에서 따라하고 있는 사람의 영상도 동일하구요. 이 사람은 물론 여기에 대한 설명은 없습니다. Blazor 8 Full Stack으로 Hybrid, Maui 많은 양의 영상을 올리고 있음에도…

제가 부정확하게 알고 있었던 사실이 있네요.
IE만 세션쿠키를 브라우저가 닫힐 때 삭제하고 나머지 브라우저들은 세션이 종료되어도 바로 세션쿠키를 정리하지 않는다고 하네요.
Blazor 키워드만 너무 살펴보았었나봅니다.
Circuit Class로 체크해서 세션을 날리던가, 쿠키 만료를 짧게하고 슬라이딩 옵션을 걸어서 위험을 최소화할 수 밖에 없겠네요.
신경써주셔서 감사합니다.
꾸뻑.