브라우저에 쿠키가 등록이 안되는 문제

안녕하세요 .net과 blazor를 이용해서 공부를 하고 있는 학생입니다…
간단한 웹 프로젝트를 만들고 있는 와중에 로그인 관련해서 막히는 부분이 있어서 이렇게 질문 드립니다.
문제의 핵심은 postman이나 중단점을 찍으면서 보면 서버쪽에서는 쿠키가 생성이 잘 되는 것을 확인 했는데 생성된 쿠키가 정작 브라우저 개발자 도구에는 없는 것이 문제입니다…

먼저 컴포넌트 에서 로그인 api를 요청하면 해당 메서드를 통해 api컨트롤러에 요청을 보냅니다. 밑에 token부분은 쿠키를 이용하다가 안되서 token도 써보고 이것저것 하느라 정리를 못해서 부득이하게 놔둔건데 무시하셔도 될 것 같습니다…

        public async Task<string> Login(EmployeeLoginModel emp)
        {
            const string baseUrl = "api/auth/login";

            var response = await _httpClient.PostAsJsonAsync(baseUrl, emp);

            if (!response.IsSuccessStatusCode)
            {
                return "";
            }   

            var token = await response.Content.ReadAsStringAsync();

            return token;
        }

그리고 api컨트롤러 부분 코드입니다.

        [HttpPost("login")]
        public async Task<ActionResult<string>> SignInAsync([FromBody] EmployeeLoginModel emp)
        {
            var cosmosEmployee = await _employeeService.Login(emp);

            if (cosmosEmployee is null)
            {
                return Forbid();
            }

            var claims = new List<Claim>
            {
                new Claim(ClaimTypes.NameIdentifier, emp.Id),
                new Claim(ClaimTypes.Name, cosmosEmployee.Name),
                new Claim(ClaimTypes.Gender, cosmosEmployee.Gender),
                new Claim(ClaimTypes.Role, cosmosEmployee.DepartmentId)
            };

            var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
            var principal = new ClaimsPrincipal(identity);

            await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(principal), new AuthenticationProperties
            {
                IsPersistent = false,
                
            });
            
            return Ok("success");
        }

그리고 program.cs부분에 쿠키의 옵션 값을 설정한 부분입니다.

builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options =>
{
    options.Cookie.Name = "auth";
    options.LoginPath= "/";
    options.Cookie.SameSite = SameSiteMode.Strict;
    options.Events.OnRedirectToAccessDenied = context =>
    {
        context.Response.StatusCode = 403;
        return Task.CompletedTask;
    };
});

옵션 값에서 samesite옵션을 none으로도 줘보고 다른 HttpOnly라던지 여러가지 속성도 설정해봤는데도 여전히 브라우저에서 쿠키를 확인할수가 없습니다…

PostMan을 통해서 api요청을 해보고 생성된 쿠키값을 제가 브라우저 개발자도구에 가서 등록을 하고 새로고침을 하면 AuthorizeView를 통해 인증된 컴포넌트가 출력이 되는게 확인은 되는데 왜 브라우저에서 로그인 버튼을 누르면 쿠키가 등록이 안되는지 이유를 모르겠습니다…

위 Login 메소드는 .razor 파일에서 처리되는 것인가요?
아니면 .cshtml 파일에서 처리되는 것인지요?

그리고, blazor server app 이신지? 아니면 blazor wasm 인지요?

2 Likes

우선 답변주셔서 감사합니다 !!
지금 사용중인 환경은 blazor webassembly입니다! 그리고 해당 메소드는 컨트롤러 클래스를 만들어 해당 클래스에서 처리하고 있습니다.

IsPersistent = true로 해보시고 테스트해보시겠습니까? 얼핏봐선 만료시간을 설정 안해서 생기는 이슈 같은데요. 아니면 AddCookie할때 options.ExpireTimeSpan = TimeSpan.FromMinutes(20); 만료시간을 넣어보시고요

블레이저 서버는 웹소켓 커넥션 기반이라, 전통적인 http 커넥션 기반의 웹앱과는 다릅니다.

로그인 API는 http 커넥션 기반이라, 브라우저나 포스트맨으로 직접 접속하면 http 응답에 쿠키를 넣어 응답합니다. 브라우저나 포스트맨에서 이를 확인할 수 있습니다.

그런데, 웹소켓 커넥션(SignalR 이 기반하는 통신)상태에서는 http 응답을 못 보냅니다.

브라우저 => Http 커넥션 => (업그레이드) => 웹소켓 커넥션(블레이저 서버 컨텍스트)

컴포넌트의 Login 메서드에서 HttpClient 로 api 에 접속하여 쿠키를 받아도 http 응답으로 연결되지 않는 이유입니다.

포스트 맨으로 받은 쿠키값을 수동으로 등록했을 때, AuthoizeView 가 동작하는 이유는 Http 통신할 때 인증 미들웨어에 의해 HttpContext.User 가 등록되고, 이를 AuthenticationStateProvider(가 다시 AuthorizeView) 에게 전달했기 때문입니다.

컴포넌트에서 로그인이 성공했을 때, AuthozieView 가 동작하게 만드는 방법은 두 가지입니다.

  1. 로그인 상태를 웹소켓 컨텍스트에서 저장.
    Login 메서드에서, HttpContext.SignIn 을 호출하여, HttpContext.User 를 업데이트 시킵니다.
    그런 후, NavigationManager 로 동일 주소로 다시 접속합니다.
    네비게이션이 갱신될 때, AuthenticationStateProvider 도 갱신되기 때문에, AuthorizeView 도 갱신됩니다. (api에서 이미 호출했기 때문에, 재접속만으로 갱신될 것입니다)

  2. 로그인 상태를 쿠키에 저장

login 메서드에서 자바 스크립트를 호출하는데, 그 내용은 로그인 api로 접속하고, returnUrl 로 복귀하는 것입니다.

브라우저에서 실행되는 자바스크립트로 api에 접속하기 때문에, 브라우저에 쿠키가 저장됩니다. 이후 다시 returnUrl 로 접속할 때, 쿠키가 함께 제공되기 때문에, 위 1번 경로를 따라 갑니다.

5 Likes

답변을 하다 딴 짓을 해서, 이 댓글이 있었는지 몰랐네요. ^^

쿠키로 저장하고 싶다면, NavigationManager를 통해 api 로 접속하는게 간편합니다.

NavigationManager가 해당 주소를 routable 컴포넌트에서 찾지 못하면, 브라우저에게 넘기는 특징이 있습니다. 그러면, 브라우저가 api에 접속하기 때문에 쿠키가 저장됩니다.

이때, AuthenticationStateProvider 를 커스텀 정의해야 하는데, 자바 스크립트를 통해 브라우저의 쿠키 저장소에 접근해서 값을 읽어와야 합니다.

두번째로, httpClient 를 통해 읽은 값을 로컬 저장소나 세션 저장소에 저장하는 것입니다.

이때에도, AuthenticationStateProvider 를 커스텀 정의해야 하는데, 자바 스크립트를 통해 브라우저의 로컬/세션 저장소에 접근해서 값을 읽어와야 합니다.

4 Likes

답변 감사합니다! 제가 사용 중인 환경을 blazor webassembly라고 했는데 제가 프로젝트를 생성했을 때 blazor web app 이걸로 생성을 했는데 blazor web app이라는 것이 sever app이랑 webassembly를 둘 다 사용하는 걸로 알고 있습니다. 그렇다면 제 프로젝트 현재 구성이 클라이언트 즉 컴포넌트에 ClientService 클래스를 인젝터 시키고 해당 서비스의 api 호출 메서드를 실행하는데요 여기서 ClientService에서는 Uri를 지정하여 HttpClient를 통해 controller에 접근하여 api를 실행시키는 형식입니다. 해당 api 코드는 본문에 api 컨트롤러 부분 코드인데요 이러한 형식의 프로젝트에서는 첫 번째로 달아주신 댓글을 참고해서 문제를 해결하면 되는 걸까요???

바쁘신 시간 쪼개서 답변 주셔서 감사합니다! IsPersistent = true이거와 만료시간 설정도 해보았는데 해결되지는 않았습니다 ㅠㅠ

ㅠㅠ

8.0 도 와즘 코드와 서버 코드의 원리는 동일합니다.

클라이언트 측 컴포넌트의 코드(와즘 코드)는 브라우저 내부에서 실행되는 하나의 스레드에 불과합니다. 따라서, 와즘 코드가 보유한 HttpClient 객체의 응답 결과는 브라우저 저장소와 아무런 상관이 없습니다.

와즘 코드 입장에서 브라우저 저장소에 뭔가를 읽고/쓰고 싶다면, 브라우저에게 요청을 해야 합니다. 즉, 자바스크립트가 필요한 것이죠.
쿠키에 읽고 쓰는 .js 만드시고, IJsInterop 을 통해 와즘 코드에서 호출하는 방법을 사용하면 됩니다.

1 Like

바쁜시간 쪼개서 답변 주셔서 감사합니다!! 한번 답변해주신 댓글들 참고해서 문제 해결 해보겠습니다 ㅎㅎ 혹시 하다가 막히는 부분이 있으면 다시 댓글 달아도 괜찮을까요???

1 Like

저보다는 Bing 신에게 … '^^;

https://sl.bing.net/2bJ04CYQN2

2 Likes

헉 감사합니다… Bing 신 만세!!