보안 수업 첫 날. Section3에서 가장 중요한 수업을 하나 고르면 누구든 이 수업을 고른다고 할 정도로 중요한 수업이다. 이번주는 특별 주간으로 부를 정도로 중요한 내용들이 몰려있다. Cookie와 Session. 엔지니어 생활하면서 어렴풋한 개념은 알았지만 직접 다루는건 쉽지 않구나. 명령어도 어색하고 MVC 패턴도 아직 살짝 어색하다. 그 상태에서 보안을 때려넣으니.. 와우! 하루 배우고 넘어간 Sequelize도 튀어나온다. 이건 복습을 안하면 도저히 따라갈 수 없다.
스프린트도 쉽지 않았다. 변명을 하자면, 테스트 케이스가 느슨하게 짜여있어서 실제로 Header에 Cookie가 들어있지 않아도 들어있다고 통과가 된다. 이걸 몰라서 한~참을 해맸다.
axios
사용에 대해서도 귀띔 해줬다면 이렇게 해매진 않았다. 오늘은 페어와 같이 너무 해매서 정신적으로 지치는 하루다. 그래도 해매면서 배운다고.. 차라리 한 번에 성공하지 못한게 오히려 득이 되지 않을까? 내가 많이 부족하고 보충해야 된다고 느끼는 계기가 되지 않을까? 조금은 나에게 위로를 해본다.
이번 포스팅은 실습 과정에서 놓쳤던 부분을 정리해보려고 한다. 부족한 부분은 몇번씩 반복해야겠다.
쓰면서 궁금해졌는데 여러개의 클라이언트가 있을 때, 전체 세션을 조회하려면 어떻게 해야될까?
세션 등록과 쿠키를 전송하는 부분은 허무할 정도로 쉽다. sessionId는 userId로 지정하고 값 포함해서 보내주면 끝.
req.session.userId = req.body.userId;
res.status(200).json({ data: userInfo, message: 'ok' }).end();
레퍼런스 코드는 save()
메소드의 콜백함수로 들어간다. 공식 문서에서는 HTTP 응답이 끝날 때 자동으로 호출되기 때문에 따로 부를 필요가 없다.
This method is automatically called at the end of the HTTP response if the session data has been altered (though this behavior can be altered with various options in the middleware constructor). Because of this, typically this method does not need to be called.
session - expressjs
req.session.save(function () {
req.session.userId = userInfo.userId;
res.json({ data: userInfo, message: 'ok' });
});
req.session 내의 키값을 지우는 것보다 (delete 메소드) req.session.destroy()
메소드로 명시적으로 지울 수 있다. 기본 값은 keep()
이며 세션을 유지한다.
req.session.destroy();
res.json({ data: null, message: 'ok' });
Frontend와 Backend 도메인 주소가 다른 경우 Cookie 전달이 되지 않는다. 특히 Network 탭에서 Response Header에 Set-Cookie는 있는데 Application 탭에서 Cookie가 보이지 않는다. 헤더의 옵션 조정으로 이를 해결할 수 있다.
헤더에 withCredentials: true
옵션으로 보안 통신 중 다른 도메인(Cross) 사이의 쿠키 기능을 활성화한다. 현재 실습 환경은 클라이언트와 서버의 도메인이 다른 상황이다.
XMLHttpRequest.withCredentials 속성은 쿠키, 권한 부여 헤더 또는 TLS 클라이언트 인증서와 같은 자격 증명을 사용하여 사이트 간 액세스 제어 요청을 만들어야 하는지 여부를 나타내는 부울 값입니다.
withCredentials 설정은 동일 사이트 요청에 영향을 미치지 않습니다.
withCredentials가 true로 설정되어 있지 않으면 자신의 도메인에 대한 쿠키 값을 설정할 수 없습니다. withCredentials를 true로 설정하여 얻은 타사 쿠키는 여전히 동일 출처 정책을 따르므로 document.cookie 또는 응답 헤더를 통해 요청하는 스크립트에서 액세스할 수 없습니다.
XMLHttpRequest.withCredentials - mozilla
axios
.post(
'https://localhost:4000/users/login',
{
userId: this.state.username,
password: this.state.password,
},
{ 'Content-Type': 'application/json', withCredentials: true } // <------
)
이 옵션을 끄면 첫번째 로그인 요청에서 아래의 에러가 발생한다. 자주 보던 에러다. CORS 정책에 위반된다는 내용이며 'XMLHttpRequest에 의해 시작된 요청의 자격 증명 모드는 withCredentials 속성에 의해 제어된다'고 한다.
Access to XMLHttpRequest at 'https://localhost:4000/users/login' from origin 'https://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
아래는 공식 문서에서 언급하는 Access-Control-Allow-Credentials 옵션에 대한 설명이다. 요약하면 다른 도메인간 헤더를 포함할 수 있게 한다.
응답헤더 Access-Control-Allow-Credentials 는 요청의 자격증명 모드(Request.credentials)가 "include" 일때, 브라우저들이 응답을 프로트엔드 자바스트립트 코드에 노출할지에 대해 알려줍니다.
요청의 자격증명 모드가 (Request.credentials)가 "include" 일 때, Access-Control-Allow-Credentials 값이 true 일 경우에만 브라우저들은 프로트엔드 자바스트립트에 응답을 노출 할 것입니다.
Access-Control-Allow-Credentials - mozilla
credentials: Configures the Access-Control-Allow-Credentials CORS header. Set to true to pass the header, otherwise it is omitted.
cors - express
origin을 *
로 적으면 안된다. 정확하게 적어야 쿠키가 들어간다. express에선 true도 받아준다.
로그인 요청이 체인으로 연결되어서 2번 이루어지는데 그 과정에서 임시 헤더가 만들어져 CORS 정책에 위반한다. 개발자 도구에서 보면 첫 Login Post 요청은 넘어가지만 이어지는 userinfo Get 요청은 CORS 에서 막힌다.
그 이유는 origin 옵션이 *
일 때는 'Access-Control-Allow-Origin' 옵션이 지원되지 않는다.
Reason: Credential is not supported if the CORS header ‘Access-Control-Allow-Origin’ is *
app.use(
cors({
origin: 'https://localhost:3000', // <-----
methods: ['GET', 'POST', 'OPTIONS'],
credentials: true, // <------
})
);