Node.js JSONWEBTOKEN 사용법 정리
2018-12-04 16:45:11
# Node.js
# Auth
세션 데이터를 token으로 발급하는 형태.
장점은 서버가 부담을 안 받는다. 그러나 역시 단점은 서버에서 제어가 안됨
페이스북이나 기타 SNS의 access_token이 이런 형태.
테스트는 심플하게 해본다.
- 클라이언트에서 sdk로 페이스북 엑세스 토큰, 유저 ID를 받아서 서버에 보낸다.
- 서버에서는 받은 토큰, ID를 검증을 한다.
- 레디스에 있으면 jwt 토큰 + 해서 리턴, 없으면 유저 프로필을 저장 후 토큰 + 해서 리턴
JWT
jwt는 jsonwebtoken을 통해 쉽게 처리가 가능하다.
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| const jwt = require('jsonwebtoken');
const secret = 'nyancat (=^・ェ・^=))ノ彡☆';
const makeToken = (id) => { const payload = { id }; const options = { issuer: 'delryn', expiresIn: '12h', }; return jwt.sign(payload, secret, options); };
const verify = (req, res, next) => { const checksum = new Promise((resolve, reject) => { jwt.verify(req.body.token, secret, (err, decode) => { if (err) return reject(err); return resolve(decode); }); });
return checksum.then((result) => { req.decode = result; return next(); }).catch((err) => { console.log(err.message); return res.status(401).send('jwt verify fail'); }); };
exports.makeToken = makeToken; exports.verify = verify;
|
payload에는 쉽게 말해 세션에 담을 꺼 넣어주면 됨.
option에는 issuer, expiresIn 이외에도 넣을 수 잇는 규칙이 잇는데 이건 공홈에 ㄱ
sign을 통해 일종의 hash 값이 생긴다.
verify는 미들웨어로 작성을 한건데 기타 다른 API를 요청올 때 토큰을 읽어서 복호화 후
유저 세션을 보는 용도.
한 가지 알아두어야 할 점은 만료된 토큰의 경우도 에러로 받는다. 적절하게 예외 처리 요망.
페이스북
클라이언트는 귀찬아서 패스
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| const axios = require('axios'); const redis = require('redis'); const jwt = require('./jwt');
const redisClient = redis.createClient(); const FACEBOOK_URL = 'https://graph.facebook.com/v3.2/'; const { FACEBOOK_APP_TOKEN } = process.env;
const accessTokenVerify = (userId, accessToken) => new Promise((resolve, reject) => { const requestURL = `${FACEBOOK_URL}debug_token?input_token=${accessToken}&access_token=${FACEBOOK_APP_TOKEN}`; axios.get(requestURL).then((response) => { const body = response.data; if (body.data.is_valid && body.data.user_id === userId) return resolve(); return reject(new Error('facebook verify fail')); }).catch((err) => reject(err)); });
const getFaceebookProfile = (accessToken) => new Promise((resolve, reject) => { const requestUrl = `${FACEBOOK_URL}me?fields=id,name,picture&access_token=${accessToken}`; axios.get(requestUrl).then((response) => resolve(response.data)).catch((err) => reject(err)); });
const setUser = (userId, userData) => new Promise((resolve, reject) => { redisClient.hset('profile', userId, JSON.stringify(userData), (err) => { if (err) return reject(err); return resolve(); }); });
const getUser = (userId) => new Promise((resolve, reject) => { redisClient.hget('profile', userId, (err, data) => { if (err) return reject(err); if (!data) return resolve(); return resolve(JSON.parse(data)); }); });
const login = (userId, accessToken) => new Promise((resolve, reject) => { accessTokenVerify(userId, accessToken).then(async () => { try { const userData = await getUser(userId); if (typeof userData !== 'undefined') { userData.token = jwt.makeToken(userId); return resolve(userData); } const facebookProfile = await getFaceebookProfile(accessToken); await setUser(userId, facebookProfile); facebookProfile.token = jwt.makeToken(userId); return resolve(facebookProfile); } catch (err) { return reject(err); } }).catch((err) => reject(err)); });
module.exports = login;
|
페이스북 API 응답
페이스북 API의 응답도 정리 해놓는다.
엑세스 토큰이 만료 된 경우
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| { data: { app_id: '내 페북 앱 아이디', type: 'USER', application: '내 페북 앱 이름', data_access_expires_at: 1551621795, error: { code: 190, message: 'Error validating access token: Session has expired on Monday, 03-Dec-1808:00:00 PST. The current time is Tuesday, 04-Dec-18 06:17:26 PST.', subcode: 463 }, expires_at: 1543852800, is_valid: false, scopes: [ 'email', 'public_profile' ], user_id: '이 앱의 내 유저 ID' } }
|
엑세스 토큰 정상인 경우
1 2 3 4 5 6 7 8 9 10
| { data: { app_id: '내 페북 앱 아이디', type: 'USER', application: '내 패북 앱 이름', data_access_expires_at: 1551621795, expires_at: 1543939200, is_valid: true, scopes: [ 'email', 'public_profile' ], user_id: '이 앱의 내 유저 ID' } }
|
프로필
1 2 3 4 5 6 7 8 9 10
| { id: '이 앱의 내 유저 ID', name: '내 이름', picture: { data: { height: 50, is_silhouette: false, url: '내 프로필 사진 url', width: 50 } } }
|