다시 API 서비스를 제공하는 입장 (localhost:8002) 이 되어봅시다.
NodeBird 앱이 아닌 다른 클라이언트가 NodeBird의 데이터를 가져갈 수 있게 해야하는 만큼 별도의 인증 과정이 필요합니다.
이 책은 JWT 토큰으로 인증하는 방법을 사용합니다.
JWT는 JSON Web Token의 약어로, JSON 형식의 데이터를 저장하는 토큰입니다.
JWT는 다음과 같이 세 부분으로 구성되어 있습니다.
• 헤더 (HEADER): 토큰 종류와 해시 알고리즘 정보가 들어있습니다.
• 페이로드 (PAYLOAD): 토큰의 내용물이 인코딩된 부분입니다.
• 시그니처 (SIGNATURE): 일련의 문자열이며, 시그니처를 통해 토큰이 변조되었는지 여부를 확인할 수 있습니다.
시그니처는 JWT 비밀키로 만들어집니다.
이 비밀키가 노출되면 JWT 토큰을 위조할 수 있으므로 비밀키를 철저히 숨겨야 합니다.
시그니처 자체는 숨기지 않아도 됩니다. 또한 JWT에는 민감한 내용을 넣으면 안된다.
우측 하단의 "secret" 부분이 JWT 비밀키입니다.
오른쪽에서 볼 수 있듯이, 페이로드 부분이 노출되어 내용을 알 수 있습니다.
저런 토큰을 남에게 보내면 안되겠죠? 그럼 내용이 노출되는 토큰을 왜 사용할까요?
모순적이지만, 내용이 들어있기 때문입니다. 만약 내용이 없는 랜덤한 토큰이라고 생각해봅시다.
랜덤한 토큰을 받으면 토큰의 주인이 누구인지, 그 사람의 권한은 무엇인지를 매 요청마다 체크해야 합니다.
이러한 작업은 보통 데이터베이스를 조회해야하는 복잡한 작업인 경우가 많습니다.
JWT 토큰은 JWT 비밀키를 알지 않는 이상 변조가 불가능합니다.
변조한 토큰은 시그니처를 비밀키를 통해 검사할 때 들통나니까요.
변조할 수 없으므로 내용물이 바뀌지 않았는지 걱정할 필요가 없습니다.
다시 말하면 내용물을 믿고 사용할 수 있습니다.
즉, 사용자 이름, 권한 같은 것을 넣어두고 안심하고 사용해도 된다는 것이죠.
단, 외부에 노출되어도 좋은 정보에 한해서입니다.
비밀번호를 제외하고 사용자의 이메일이나 사용자의 권한 같은 것들을 넣어두면
데이터베이스 조회 없이도 그 사용자를 믿고 권한을 줄 수 있습니다.
JWT 토큰의 단점은 용량이 크다는 것입니다.
npm install jsonwebtoken
이제 JWT를 사용해서 본격적으로 API를 만들어보겠습니다.
다른 사용자가 API를 사용하려면 JWT 토큰을 발급받고 인증받아야 합니다.
이는 대부분의 라우터에 공통적으로 해당하는 부분이므로 미들웨어로 만들어두는 게 좋습니다.
JWT_SECRET를 .env에 저장
요청 헤더에 저장된 토큰(req.headers.authorization)을 사용합니다.
사용자가 쿠키처럼 헤더에 토큰을 넣어서 보낼 것입니다.
jwt.verify 메서드로 토큰을 검증할 수 있습니다.
메서드의 첫 번째 인수로는 토큰을, 두 번째 인수로는 토큰의 비밀키를 넣습니다.
토큰의 비밀키가 일치하지 않는다면 인증을 받을 수 없습니다.
그런 경우에는 에러가 발생하여 catch 문으로 이동하게 됩니다.
또한, 올바른 토큰이더라도 유효 기간이 지난 경우에도 catch 문으로 이동합니다.
유효 기간 만료 시 419 상태 코드를 응답하는데, 코드는 400 번대 숫자 중에서 마음대로 정해도 됩니다.
인증에 성공한 경우에는 토큰의 내용이 반환되어 req.decoded에 저장됩니다.
토큰의 내용은 조금 전에 넣은 사용자 아이디와 닉네임, 발급자, 유효 기간 등입니다.
req.decoded를 통해 다음 미들웨어에서 토큰의 내용을 사용할 수 있습니다.
토큰을 발급하는 라우터 (POST /v1/token)와 사용자가 토큰을 테스트해 볼 수 있는 라우터 (GET /v1/test)를 만들었습니다.
라우터의 이름은 v1로, 버전 1이라는 뜻입니다.
라우터에 버전을 붙인 이유는 한 번 버전이 정해진 후에는 라우터를 함부로 수정하면 안되기 때문입니다.
다른 사람이나 서비스가 기존 API를 사용 중임을 항상 염두에 두어야 합니다.
API 서버의 코드를 바꾸면 API를 사용하는 다른 프로그램에 영향을 미칩니다.
특히 기존에 있던 라우터가 수정되는 순간 API를 사용하는 프로그램들이 오작동할 수 있습니다.
따라서 기존 사용자에게 영향을 미칠 정도로 수정해야 한다면,
버전을 올린 라우터를 새로 추가하고 이전 API를 쓰는 사람들에게는 새로운 API가 나왔음을 알리는 것이 좋습니다.
이전 API를 없앨 때도 어느 정도 기간을 두고 미리 공지하여 사람들이 다음 API로 충분히 넘어갔을 때 없애는 것이 좋습니다.
POST /v1/token 라우터에서는 전달받은 클라이언트 비밀키로 도메인이 등록된 것인지를 먼저 확인합니다.
등록되지 않은 도메인이라면 에러 메시지로 응답하고, 등록된 도메인이라면 토큰을 발급해서 응답합니다.
토큰은 jwt.sign 메서드로 발급받을 수 있습니다. 다음 코드를 살펴봅시다.
sign 메서드의 첫 번째 인수는 토큰의 내용(사용자 아이디, 닉네임)입니다.
두 번째 인수는 토큰의 비밀키입니다.
이 비밀키가 유출되면 다른 사람이 NodeBird 서비스의 토큰을 임의로 만들어낼 수 있으므로 조심해야합니다.
세 번째 인수는 토큰의 설정입니다. 유효기간을 1분으로 설정하고, 발급자를 "nodebird"로 정했습니다.
"1m"으로 표기된 부분은 ms 라이브러리의 형식을 사용한 것이며, 그냥 60 * 1000처럼 밀리초 단위로 적어도 됩니다.
발급되고 나서 1분이 지나면 토큰이 만료되므로, 만료되었다면 토큰을 재발급받아야합니다.
유효기간은 서비스 정책에 따라 알아서 설정하면 됩니다.
GET /v1/test 라우터는 사용자가 발급받은 토큰을 테스트해볼 수 있는 라우터입니다.
토큰을 검증하는 미들웨어를 거친 후, 검증이 성공하면 토큰의 내용을 응답으로 보냅니다.
라우터의 응답을 살펴보면 모두 일정한 형식을 갖추고 있습니다.
JSON 형태에 code, message 속성이 존재하고, 토큰이 있는 경우 token 속성도 존재합니다.
이렇게 일정한 형식을 갖추면 응답을 받는 쪽에서 처리하기가 좋습니다.
code는 HTTP 상태 코드를 사용해도 되고, 임의로 숫자를 부여해도 됩니다. 일관성만 있다면 문제없습니다.
사용자들이 code만 봐도 어떤 문제인지 알 수 있게 하면 됩니다.
code를 이해하지 못할 경우를 대비하여 message도 같이 보냅니다.
code가 200번대 숫자가 아니면 에러이고, 에러의 내용은 message에 담아 보내는 것으로 현재 API 서버의 규칙을 정했습니다.
방금 만든 라우터를 서버에 연결합니다.
Note: JWT 토큰으로 로그인하려면
최근에는 JWT 토큰을 사용해서 로그인하는 방법이 많이 사용되고 있습니다.
세션을 사용하지 않고 로그인할 수 있기 때문입니다.
로그인 완료 시 세션에 데이터를 저장하고 세션 쿠키를 발급하는 대신 JWT 토큰을 쿠키로 발급하면 됩니다.
다음과 같이 authenticate 메서드의 두 번째 인수로 옵션을 주면 세션을 사용하지 않을 수 있습니다.
router.post ('/Login', isNotLoggedIn, (req, res, next) => {
passport.authenticate('local', {session:false}, (authError, user, info) = > {
if(authError) {
...
세션에 데이터를 저장하지 않으므로 serializeUser와 deserializeUser는 사용하지 않습니다.
그 후 모든 라우터에 verifyToken 미들웨어를 넣어 클라이언트에서 보낸 쿠키를 검사한 후,
토큰이 유효하면 라우터로 넘어가고, 그렇지 않으면 401이나 419 에러를 응답하면 됩니다.
사용자 권한 확인을 위해 데이터베이스를 사용하지 않으므로 (JWT 토큰 내부에 넣어두면 됩니다)
서비스의 규모가 클수록 데이터베이스의 부담을 줄일 수 있습니다.
Note: 클라이언트 환경에서 JWT를 사용하고 싶다면,
클라이언트 환경에서 Process.env.JWT_SECRET (비밀키)이 노출되면 안됩니다.
그럼에도 verify나 sign 같은 메서드를 사용해야 한다면, RSA 같은 양방향 비대칭 암호화 알고리즘을 사용해야 합니다.
서버 환경에서는 비밀키를 사용하고, 클라이언트 환경에서는 공개키를 사용하는 방식으로
클라이언트에서 비밀키가 노출되는 것을 막을 수 있습니다.
공식 문서(https://www.npmjs.com/ package/j sonwebtoken)에서 PEM 키를 사용하는 부분을 참고하면 됩니다.
'clone toy projects > node_express_sns' 카테고리의 다른 글
SNS API 서버 만들기 (0) | 2023.08.25 |
---|---|
다른 서비스에서 호출하기 (0) | 2023.08.25 |
프로젝트 구조 갖추기 (0) | 2023.08.25 |
API 서버 이해 (0) | 2023.08.25 |
팔로잉과 해시태그 (0) | 2023.08.24 |