...
API(Application Programming Interface) : 다른 애플리케이션에서 현재 프로그램의 기능을 사용할 수 있게 허용하는 접점
이 장에서는 NodeBird 앱의 REST API 서버를 만든다.
API 서버는 프런트엔드와 분리되어 운영되므로 모바일 서버로도 사용할 수 있다.
특히 JWT 토큰은 모바일 앱과 노드 서버 간에 사용자 인증을 구현할 때 자주 사용된다.
웹 API는 다른 웹 서비스의 기능을 사용하거나 자원을 가져올 수 있는 창구.
위와 같은 서버에 API를 올려서 URL을 통해 접근할 수 있게 만든 것을 웹 API 서버라고 한다.
필요 모듈
UUID 모듈
# npm
npm install uuid
# v4 만 설치
npm install uuid4
[NODE] 📚 UUID 모듈
UUID 란? UUID는 Universally Unique IDentifier의 약자로, 전세계에 하나밖에 없는 ID라는 뜻이다. 이런 ID는 고유하기 때문에 서버에서 사용자들에 UUID를 붙여서 구분하는 등 여러 방면에서 유용하게 쓰일
inpa.tistory.com
jsonwebtoken 모듈
Node에서 JWT를 사용하기 위한 모듈이다.
$ npm install jsonwebtoken
jsonwebtoken 모듈에는 크게 두가지 메소드가 있다.
// sign을 통해 JWT 토큰 생성하는 메소드
jwt.sign(payload, secretKey, options)
// verify를 통해 사용자 토큰 인증하고 payload를 디코딩하여 데이터를 반환하는 메소드
decoded = jwt.verify(token, secretKey);
API 서버 구성
두가지로 구성한다.
하나는 API를 요청해서 가져와 그 데이터를 쓰는 서버 (클라이언트)
다른하나는 API요청을 받으면 데이터를 제공하는 API 서버 이다.
API 제공하는 서버 (http://localhost:8002)
api요청 해서 갖다 쓰는, 써드파티 서버 (클라이언트 사용자) (http://localhost:4000)
토큰을 이용한 API 통신 순서
1. 도메인 등록&신청 하기
1) http://localhost:8002 API서버에 접속해서, 로그인하고 도메인 등록 정보를 입력하고 [저장] 버튼을 누르게 되면, http://localhost:8002/domain 으로 post요청이 가게 된다.
2) 그럼 ORM을 실행해 도메인 정보를 DB에 저장한다.
이때 쓰이는게 uuidv4() 메소드인데, 랜덤적인 유일한 문자열을 생성해 누가 누군인지 구분하는 용도로 쓰인다.
유일한 랜덤적인 문자열(UUID)와 사용자 그리고 도메인을 저장한다.
// POST | http://localhost:8002/domain 요청오면 실행되는 라우터
router.post('/domain', isLoggedIn, async (req, res, next) => {
try {
await Domain.create({
UserId: req.user.id,
host: req.body.host,
type: req.body.type,
clientSecret: uuidv4(),
});
res.redirect('/');
} catch (err) {
console.error(err);
next(err);
}
});
module.exports = router;
3) 그렇게 사용자는 비밀키(대칭키)를 api 서버로부터 할당 받는다.
이 비밀키는 사용자를 식별하는데 쓰인다.
4) 이제 API서버로부터 발급받은 클라이언트 비밀키를 .env에 안전하게 저장한다
2. 써드파티 서버(클라이언트)에서 REST API로 json데이터를 api서버에 요청해서 받기
1) 써드파티 서버 홈페이지에서, 사용자가 REST API로 요청 (localhose:4000/mypost)
2) 써드파티 서버 라우터로 요청이 옴
const express = require('express');
const axios = require('axios');
const router = express.Router();
const VER = "v1"
const URL = 'http://localhost:8002/' + VER;
// 어디에서 요청을 했는지 origin 헤더 추가
// 보통 브라우저에서 서버로 보낼때는 자동으로 브라우저가 헤더에 origin을 넣어서 보내주는데, 서버에서 서버로 보낼때는 안들어간 경우가 있어서 직접 넣음
axios.defaults.headers.origin = 'http://localhost:4000';
//* 발급 받은 api키를 넘겨서 AccessToken을 얻고 다시 그걸 넘겨서 api.payload를 얻는 메소드
const request = async (req, api) => {
try {
// 세션에 토큰이 없으면,
if (!req.session.jwt) {
// 발급받은 api키를 헤더로 넘겨서 토큰 발급 신청
const tokenResult = await axios.post(`${URL}/token`, {
clientSecret: process.env.CLIENT_SECRET, // .env에 저장한 비밀키
});
// 세션에 토큰 저장
req.session.jwt = tokenResult.data.token;
}
// 토큰을 넘겨 인증 받고 API 요청
return await axios.get(`${URL}${api}`, {
headers: { authorization: req.session.jwt },
});
} catch (error) {
// 토큰 만료시 토큰 재발급 받기.
if (error.response.status === 419) {
delete req.session.jwt; // delete 연산자는 객체의 속성을 제거
console.log('토큰 만료. 토큰 재발급');
return request(req, api); // 토큰이 없으면 자동으로 재발급. 재귀함수로 간단히 처리
}
// 419 외의 다른 에러면
return error.response;
}
};
//* 내 포스팅을 얻기 위해 api요청하는 페이지
router.get('/mypost', async (req, res, next) => {
try {
// 키가 들어있는 req객체와 restapi 주소를 넘겨서 payload를 넘겨 받음
const result = await request(req, '/posts/my');
res.json(result.data);
} catch (error) {
console.error(error);
next(error);
}
});
만일 세션에 가지고있는 토큰이 없다면, 아까 .env에 저장한 비밀키를 api 서버에 넘겨서 JWT토큰 신청
3) api서버의 http://localhost:8002/token 으로 요청이 옴
const express = require('express');
const jwt = require('jsonwebtoken');
const { verifyToken, deprecated } = require('./middlewares');
const { Domain, User, Post, Hashtag } = require('../models');
const router = express.Router();
//* 토큰 발행
router.post('/token', async (req, res) => {
const { clientSecret } = req.body;
try {
// 도메인 검사
const domain = await Domain.findOne({
where: { clientSecret },
include: {
model: User,
attribute: ['nick', 'id'],
},
});
if (!domain) {
return res.status(401).json({
code: 401,
message: '등록되지 않은 도메인입니다. 먼저 도메인을 등록하세요',
});
}
// 등록된 도메인이면, 토큰 발급 jwt.sign( 페이로드, 비밀키, 옵션 )
const token = jwt.sign(
{
// payload 데이터
id: domain.User.id,
nick: domain.User.nick,
},
process.env.JWT_SECRET, // 비밀키
{
// 토큰 옵션
expiresIn: '1m', // 만료시간
issuer: 'nodebird', // 발급자. 위조판별용+
},
);
return res.json({
code: 200,
message: '토큰이 발급되었습니다',
token,
});
} catch (error) {
console.error(error);
return res.status(500).json({
code: 500,
message: '서버 에러',
});
}
});
//* 내 포스팅을 제공해주는 api
router.get('/posts/my', verifyToken, (req, res) => {
Post.findAll({
where: { userId: req.decoded.id }, // 토큰에 사용자 id가 들어있어 그걸 사용한다. 세션이 아니라.
})
.then(posts => {
console.log(posts);
res.json({
code: 200,
payload: posts,
});
})
.catch(error => {
console.error(error);
return res.status(500).json({
code: 500,
message: '서버 에러',
});
});
});
[토큰 발급 순서]
4) 그렇게 써드파티 서버(클라이언트)는 api서버에서 넘겨받은 토큰을 클라이언트 세션에 저장하고, 곧바로 토큰을 서버에 넘겨 json api 데이터 요청
5) 써드파티 서버 -> api 서버 axios.get요청을 해서 api서버 라우터(/posts/my)로 요청이 옴
그러면 인증 미들웨어(verifyToken)을 실행해 토큰이 올바른지 검증 한다.
이때 서버에서는 대칭키를 통해 토큰을 인증한다.
대칭키(JWT_SECRET)는 api 서버의 .env에 저장되어있다. 절대로 유출되어서는 안된다.
그렇게 인증을 완료하면, payload 데이터를 디코딩하여 미들웨어 변수(req.decoded)에 저장한다.
그리고 라우터 next()를 하게 되고, 콜백에서 payload에 써있는 정보에따라 데이터베이스를 조회하여 정보를 빼네, 그 정보들을 json형태로 써드파티 서버에게 응답하게 된다.
6) 그렇게 api서버로부터 넘겨 받은 json정보를 result변수에 저장해서 다시 홈페이지(사용자)에게 응답하면 api요청이 안료 되게 된다.
![이모티콘](https://t1.daumcdn.net/keditor/emoticon/friends1/large/002.gif)
참고로 웹사이트에서 json형식으로 위 사진처럼 이쁘게 보려면 크롬 확장 프로그램을 설치하면 된다.
다음글을 참고해보자.
💻 웹 개발에 도움되는 🧩 크롬 확장 프로그램 정리
ColorZila ColorZilla Advanced Eyedropper, Color Picker, Gradient Generator and other colorful goodies chrome.google.com 확장프로그램에서 ColorZilla를 선택하고 Page Color Picker Active를 선택합니다...
inpa.tistory.com
Reference
https://velog.io/@neity16/NodeJS-JWT-Token-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.