개발을 하다보면 서버와 클라이언트가 각각 별도의 도메인을 가지고 있는 경우가 있습니다. 이때 Fetch API를 이용해서 Cookie 를 주고 받는 방법에 대해서 알아보겠습니다.
API 생성
먼저 쿠키를 생성하는 api와 요청에 들어온 쿠키를 응답해주는 api를 작성해 줍니다.
const createError = require('http-errors');
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
const app = express();
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// 요청에 들어온 쿠키를 응답해주는 api
app.use('/send-cookie', (req, res, next) => {
res.send(req.cookies);
});
// 쿠키를 생성해주는 api
app.use('/set-cookie', (req, res, next) => {
res.cookie('name', 'wangmin', {path:'/'});
res.sendStatus(200);
});
module.exports = app;
(서버는 Express.js 프레임워크를 이용해서 구성했습니다. Express.js 사용법은 공식 사이트를 참고해주세요.)
그럼 http://localhost:3000/set-cookie, http://localhost:3000/send-cookie로 직접 접속해서 API가 제대로 동작 하는지 확인합시다.
크롬을 사용하고 계시다면 설정 - 고급 - 사이트 설정 - 쿠키 및 사이트 데이터 에서도 확인이 가능합니다.
확인했으면 쿠키를 삭제해주자.
CORS 허용
그럼 다른 도메인으로 접속해서 서버로 요청을 보내보겠습니다. 블로그에 접속해서 개발자 도구(F12 or Ctrl+Shift+I)의 Console 탭을 이용해서 Fetch 요청을 보내보겠습니다.
아래와 같은 에러가 발생하는것을 볼 수 있습니다.
> fetch('http://localhost:3000/set-cookie');
Access to fetch at 'http://localhost:3000/set-cookie' from origin 'https://min-92.github.io' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
(index):1 Uncaught (in promise) TypeError: Failed to fetch
해당 에러는 서버에서 CORS(Cross-Origin Resource Sharing) 옵션을 허용하지 않아서 발생하는 오류입니다.(CORS내용은 여기서 확인)
npm을 사용해서 cors 패키지를 설치하고 서버에 코드를 추가해줍니다.
$npm install cors
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
const app = express();
// 모든 사이트 CORS 허용
const cors = require('cors');
app.use(cors());
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// 요청에 들어온 쿠키를 응답해주는 api
app.use('/send-cookie', (req, res, next) => {
res.send(req.cookies);
});
// 쿠키를 생성해주는 api
app.use('/set-cookie', (req, res, next) => {
res.cookie('name', 'wangmin', {path:'/'});
res.sendStatus(200);
});
module.exports = app;
다시 요청을 보내보면 에러가 발생하지 않는것을 확인할 수 있습니다.
쿠키 헤더 확인
다시 Fetch API를 이용해서 쿠키가 생성되고, 전송되는지 확인해보겠습니다.
먼저 쿠키를 생성하는 요청을 보내보겠습니다.
정상정인 응답인 200이 왔지만 쿠키가 생성되지 않았습니다.
그렇다면 직접 접속해서 쿠키를 생성하고, Fetch API를 이용해서 쿠키를 전송해 보겠습니다.
설정에서 쿠키가 있는것을 확인 할 수 있지만, 서버로 쿠키가 전송되지 않았습니다.
요청과 응답의 Header를 확인해서 쿠키의 전송 유무를 확인해보겠습니다.
쿠키를 생성하는 응답에는 Set-Cookie 헤더가 있지만, 쿠키가 생성되지 않았습니다.
쿠키를 전송하는 요청에는 Cookie 헤더가 존재하지 않았습니다.
원인은 Fetch API에 있습니다. MDN을 확인해보면 아래와 같은 내용이 있습니다.
보통 fetch는 쿠키를 보내거나 받지 않습니다. 사이트에서 사용자 세션을 유지 관리해야하는 경우 인증되지 않는 요청이 발생합니다. 쿠키를 전송하기 위해서는 자격증명(credentials) 옵션을 반드시 설정해야 합니다.
그렇다면 자격증명(Credentials) 옵션을 설정해서 쿠키를 전송해봅시다.
자격증명(Credentials) 설정하기
자격증명(Credentials)이란 쿠키, authorizations 헤더, TLS 클라이언트 인증서를 뜻합니다. 자세한 내용은 여기를 확인해주세요.
Fetch API의 Credentials 옵션에는 3가지가 있습니다.
- include
- Cross-origin 호출도 자격증명을 포함.
- same-origin(default)
- Same-origin 호출만 자격증명을 포함.
- omit
- 절대 자격증명을 포함하지 않음.
우리는 cross-origin으로 요청을 보낼것이기 때문에 'include' 옵션을 사용하겠습니다.
fetch('http://localhost:3000/set-cookie', {
credentials : 'include'
});
위의 코드로 요청을 보내보면 에러가 발생하는걸 볼수 있습니다.
Access to fetch at 'http://localhost:3000/set-cookie' from origin 'https://min-92.github.io' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
모든 페이지에 대해서 cors 요청이 허용일때, credentials 옵션을 include로 설정 할 수 없다는 에러메세지 입니다. 기본적으로 credential은 cors 헤더 ‘Access-Control-Allow-Origin’ 가 '*' 을 지원하지 않습니다. (여기서 확인 가능합니다.)
Whitelist 작성하기
특정 origin만 cors 요청을 허용하기 위해서 whitelist 를 작성해 봅시다.
접근을 허용할 리스트를 만들어 배열에 작성하고 기존 코드를 수정해줍니다.
const whitelist = ['https://min-92.github.io'];
const express = require('express');
const path = require('path');
const cookieParser = require('cookie-parser');
const logger = require('morgan');
const app = express();
// 화이트 리스트만 cors 요청 허용
const cors = require('cors');
const whitelist = ['https://min-92.github.io'];
const corsOptions = {
origin: (origin, callback) => {
if (whitelist.indexOf(origin) !== -1) {
callback(null, true)
} else {
callback(new Error('Not allowed by CORS'))
}
},
credentials: true
}
app.use(cors(corsOptions));
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// 요청에 들어온 쿠키를 응답해주는 api
app.use('/send-cookie', (req, res, next) => {
res.send(req.cookies);
});
// 쿠키를 생성해주는 api
app.use('/set-cookie', (req, res, next) => {
res.cookie('name', 'wangmin', {path:'/'});
res.sendStatus(200);
});
module.exports = app;
credneitlas 옵션을 true로 설정하고, origin 이 whitelist에 없으면 오류를 발생합니다.
자, 그럼 제대로 동작 하는지 확인해봅시다.
쿠키를 생성하는 요청과 쿠키를 전송하는 요청을 보냈습니다. 그럼 요청과 응답의 헤더를 확인해보겠습니다.
요청과 응답의 헤더에 각각 아래와 같은 항목이 추가된걸 확인할 수 있습니다.
// Request Headers
Cookie : name=wangmin
// Response Headers
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: https://min-92.github.io
요청과 응답의 헤더가 제대로 동작했고, 응답의 바디를 확인해보면 쿠키가 제대로 전송된 것을 확인할 수 있습니다.
마무리
지금까지 Fetch API를 이용해서 Cross-Origin 간에 쿠키를 주고받는 법을 알아봤습니다.
요약하자면 아래와 같습니다.
-
Fetch 에 credentials : include 옵션을 준다.
fetch('domain', {
credentials : 'include'
});
-
서버에 Cors 설정을 하고 whitelist를 작성해서 특정 도메인만 요청을 보낼 수 있도록 허용한다.
const whitelist = ['https://min-92.github.io'];
const corsOptions = {
origin: (origin, callback) => {
if (whitelist.indexOf(origin) !== -1) {
callback(null, true)
} else {
callback(new Error('Not allowed by CORS'))
}
},
credentials: true
}
app.use(cors(corsOptions));
아주 간단하게 해결할 수 있는 문제지만, 제가 개발을 하면서 겪은 내용대로 작성을 하다보니 내용이 많이 길어진 감이 있습니다. 부족한 글이지만 다른 분들에게 도움이 됐으면 좋겠습니다. 감사합니다.