Json Web Token의 약자입니다.
json 포맷을 사용해 사용자의 속성을 저장하는 web token 입니다.
그리고 jwt는 주로 회원 인증을 위해 사용됩니다.
구조는 아래처럼 header, payload, signature로 이루어졌있고,
각 부분은 base64로 인코딩되어 있으며, .을 사용해 구분을 해줍니다.

헤더는 alg와 typ으로 이루어져있는데 typ은 토큰의 타입을 의미하며 alg는 signature를 해싱할 때 사용되는 알고리즘을 적어둡니다.
토큰에서 사용할 조각(claim) 들이 들어가는 곳 입니다.
등록된 클레임과 공개 클레임 비공개 클레임으로 나누어집니다.
등록된 클레임은 아래와 같습니다.
iss: issuer - 토큰 발급자
sub: subject - 토큰 제목
aud: audience - 토큰 대상자
exp: expiration - 토큰 만료일
nbf: not before - 토큰 활성 날자
iat: issued at - 토큰 발급 시간
jti: JWT ID - 토큰 식별자
그리고 공개 클레임은 uri 포맷을 이용하며
비공개 클레임은 서버 <-> 클라이언트 통신을 위해 사용자가 임의로 지정한 클레임입니다.
토큰을 검증할 때 사용하는 고유한 암호화 코드입니다.
이를 사용해서 유효한 토큰인지 검증을 하게됩니다.
본격적으로 토큰을 생성해주기 전에 base64로 인코딩 해주는 함수를 먼저 만들어줍니다.
여기서 = 을 모두 제거해주는 이유는 혹시라도 발생할 충돌을 방지해주기 위해서 입니다.
만약 url에 = 이 포함된 토큰을 넣을경우 문제가 발생할 수 있습니다.
function toBase64(obj) {
const stringifiedObj = JSON.stringify(obj);
const base64Encoded = Buffer.from(stringifiedObj).toString('base64');
const paddingRemoved = base64Encoded.replaceAll('=', '');
return paddingRemoved;
}
그리고 드디어 토큰 발급 생성입니다.
우선 헤더를 만들어줍니다.
간단합니다. alg와 typ을 넣고 base64로 인코딩하면 됩니다.
function sign(payloads, expire) {
...
const encodedHeader = toBase64({
alg: algorithm,
typ: 'JWT',
});
}
그 후 payload를 만들어줍니다.
원하는 값들을 넣고 이역시 base64로 인코딩 해줍니다.
function sign(payloads, expire) {
...
const nowTime = new Date().getTime();
const encodedPayload = toBase64({
...payloads,
iat: nowTime,
exp: nowTime + expire,
iss: issuer,
});
}
다음으로 제일 중요한 signature를 생성해줄 차례입니다.
nodejs의 crypto를 사용해 sha256으로 해싱해줍니다.
여기서 secret은 중요합니다. 지금은 그냥 넣어줬지만
검증할 때 사용되므로 안전한곳에 보관 해 두어야 합니다.
import { createHmac } from 'crypto';
function sign(payloads, expire) {
...
const encodedSignature = createHmac('sha256', 'secret')
.update(`${encodedHeader}.${encodedPayload}`)
.digest('base64')
.replaceAll('=', '');
}
마지막으로 이렇게 만든 header, payload, signature를 합치면 끝입니다.
function sign(payloads, expire) {
...
const jwt = `${encodedHeader}.${encodedPayload}.${encodedSignature}`;
return jwt;
}
아래는 전체 코드입니다.
function sign(payloads, expire) {
const encodedHeader = toBase64({
alg: algorithm,
typ: 'JWT',
});
const nowTime = new Date().getTime();
const payload = {
...payloads,
iat: nowTime,
exp: nowTime + expire,
iss: issuer,
};
const encodedPayload = toBase64(payload);
const encodedSignature = createHmac('sha256', secret)
.update(`${encodedHeader}.${encodedPayload}`)
.digest('base64')
.replaceAll('=', '');
const jwt = `${encodedHeader}.${encodedPayload}.${encodedSignature}`;
return jwt;
}
검증은 생성 과정을 반대로 하면 됩니다.
function verify(token) {
const [encodedHeader, encodedPayload, encodedSignature] = token.split('.');
const { alg, typ } = toUTF8(encodedHeader);
const payload = toUTF8(encodedPayload);
const { iat, exp, iss, email, type } = payload;
const newSignature = createHmac('sha256', secret)
.update(`${encodedHeader}.${encodedPayload}`)
.digest('base64')
.replaceAll('=', '');
if (!this.isValidToken(typ, iss, encodedSignature, newSignature, alg)) {
throw new Error('INVALID TOKEN');
}
if (this.isExpiredToken(exp, iat)) {
throw new Error('EXPIRED TOKEN');
}
return payload;
}
function isValidToken(typ, iss, encodedSignature, newSignature, alg) {
return (
typ === 'JWT' && iss === issuer && encodedSignature === newSignature && alg === algorithm
);
}
function isExpiredToken(exp, iat) {
return exp - iat <= 0;
}
한가지 눈여겨 봐야할 부분은
유효한 토큰인지 확인하는 isValidToken와
기간이 만료되었는지 확인하는 isExpiredToken 함수입니다.
만약 유효하고 기간이 만료되지 않았다면 utf8로 디코딩된 payload를 리턴해줍니다.
이렇게 완성된 토큰은 https://jwt.io/ 에서 이게 제대로된 토큰인지 확인할 수 가 있습니다.