JSON Web Token
JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties. The claims in a JWT are encoded as a JSON object that is used as the payload of a JSON Web Signature (JWS) structure or as the plaintext of a JSON Web Encryption (JWE) structure, enabling the claims to be digitally signed or integrity protected with a Message Authentication Code (MAC) and/or encrypted.
구조
- HEADER (ALGORITHM & TOKEN TYPE)
- PAYLOAD (DATA)
- VERIFY SIGNATURE
위의 각각의 내용(모두 문자열)을 Base64로 인코딩 한 후 다음과 같이 .
으로 연결한다.
정보 (payload)
Payload 부분에는 토큰에 담을 정보가 들어있습니다. 여기에 담는 정보의 한 ‘조각’ 을 클레임(claim) 이라고 부르고, 이는 name / value 의 한 쌍으로 이뤄져있습니다. 토큰에는 여러개의 클레임 들을 넣을 수 있습니다.
클레임 의 종류는 다음과 같이 크게 세 분류로 나뉘어져있습니다:
- 등록된 (registered) 클레임,
- 공개 (public) 클레임,
- 비공개 (private) 클레임
등록된 (registered) 클레임
등록된 클레임들은 서비스에서 필요한 정보들이 아닌, 토큰에 대한 정보들을 담기위하여 이름이 이미 정해진 클레임들입니다. 등록된 클레임의 사용은 모두 선택적 (optional)이며, 이에 포함된 클레임 이름들은 다음과 같습니다:
-
iss
: 토큰 발급자 (issuer) -
sub
: 토큰 제목 (subject) -
aud
: 토큰 대상자 (audience) -
exp
: 토큰의 만료시간 (expiraton), 시간은 NumericDate 형식으로 되어있어야 하며 (예: 1480849147370) 언제나 현재 시간보다 이후로 설정되어있어야합니다. -
nbf
: Not Before 를 의미하며, 토큰의 활성 날짜와 비슷한 개념입니다. 여기에도 NumericDate 형식으로 날짜를 지정하며, 이 날짜가 지나기 전까지는 토큰이 처리되지 않습니다. -
iat
: 토큰이 발급된 시간 (issued at), 이 값을 사용하여 토큰의 age 가 얼마나 되었는지 판단 할 수 있습니다. -
jti
: JWT의 고유 식별자로서, 주로 중복적인 처리를 방지하기 위하여 사용됩니다. 일회용 토큰에 사용하면 유용합니다.
공개 (public) 클레임
공개 클레임들은 충돌이 방지된 (collision-resistant) 이름을 가지고 있어야 합니다. 충돌을 방지하기 위해서는, 클레임 이름을 URI 형식으로 짓습니다.
비공개 (private) 클레임
등록된 클레임도아니고, 공개된 클레임들도 아닙니다. 양 측간에 (보통 클라이언트 <->서버) 협의하에 사용되는 클레임 이름들입니다. 공개 클레임과는 달리 이름이 중복되어 충돌이 될 수 있으니 사용할때에 유의해야합니다.
예제 Payload
{
"iss": "velopert.com",
"exp": "1485270000000",
"https://velopert.com/jwt_claims/is_admin": true,
"userId": "11028373727102",
"username": "velopert"
}
JWT 저장 위치
2줄 요약:
- Refresh token을 사용.
- cookie와 localStorage, 굳이 1개를 택하라면? localStorage 사용.
Axios로 JWT Refresh 토큰 갱신
JWT Security
We use JSON Web Tokens (JWTs) quite a lot in the OAuth and OpenID Connect world.
- JWTs used as Access Tokens
- What algorithms to use
- When to validate the token
- Always check the issuer
- Always check the audience
- Make sure tokens are used as intended
- Dealing with expiration, issued time and clock skew
- How to work with the signature
- When to use symmetric signing
- Pairwise Pseudonymous Identifiers
- Do not use JWTs for sessions
Example
decodeJWTPayload with decodeBase64URL
export function decodeBase64URL(value: string): string {
const key = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
let base64 = ''
let chr1, chr2, chr3
let enc1, enc2, enc3, enc4
let i = 0
value = value.replace('-', '+').replace('_', '/')
while (i < value.length) {
enc1 = key.indexOf(value.charAt(i++))
enc2 = key.indexOf(value.charAt(i++))
enc3 = key.indexOf(value.charAt(i++))
enc4 = key.indexOf(value.charAt(i++))
chr1 = (enc1 << 2) | (enc2 >> 4)
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2)
chr3 = ((enc3 & 3) << 6) | enc4
base64 = base64 + String.fromCharCode(chr1)
if (enc3 != 64 && chr2 != 0) {
base64 = base64 + String.fromCharCode(chr2)
}
if (enc4 != 64 && chr3 != 0) {
base64 = base64 + String.fromCharCode(chr3)
}
}
return base64
}
// Taken from: https://stackoverflow.com/questions/38552003/how-to-decode-jwt-token-in-javascript-without-using-a-library
export function decodeJWTPayload(token: string) {
// Regex checks for base64url format
const base64UrlRegex = /^([a-z0-9_-]{4})*($|[a-z0-9_-]{3}=?$|[a-z0-9_-]{2}(==)?$)$/i
const parts = token.split('.')
if (parts.length !== 3) {
throw new Error('JWT is not valid: not a JWT structure')
}
if (!base64UrlRegex.test(parts[1])) {
throw new Error('JWT is not valid: payload is not in base64url format')
}
const base64Url = parts[1]
return JSON.parse(decodeBase64URL(base64Url))
}
reservationTokenRenewal
reservationTokenRenewal(access: string, refresh: string) {
const encodedPayload = access.split('.')[1];
const payloadJsonText = atob(encodedPayload)
const parsed = JSON.parse(payloadJsonText.toString());
const hasExp = parsed.hasOwnProperty("exp");
const hasIat = parsed.hasOwnProperty("iat");
if (!hasExp || !hasIat) {
if (!hasExp) {
console.error('Missing `exp` in JWT payload');
}
if (!hasIat) {
console.error('Missing `iat` in JWT payload');
}
return;
}
// const nowUtc = moment();
// const expUtc = moment(parsed.exp * 1000);
// const iatUtc = moment(parsed.iat * 1000);
// console.debug('Access token expiration: ' + expUtc.toISOString());
const expMilliseconds = (parsed.exp * 1000 - LEEWAY_MILLISECONDS);
const timeout = expMilliseconds - Date.now();
if (timeout < 0) {
console.warn('After a while, the token will expire.');
return;
}
if (typeof this.refreshTimeoutId !== 'undefined') {
window.clearTimeout(this.refreshTimeoutId);
this.refreshTimeoutId = undefined;
}
this.refreshTimeoutId = window.setTimeout(() => {
this.refreshToken(refresh);
}, timeout);
const renewalTime = moment(Date.now() + timeout).toISOString();
console.debug(`Token renewal timeout: ${timeout}ms (${renewalTime})`);
}
Documentation
- rfc 7519
Libraries
See also
Favorite site
Online tools
- [추천] JSON Web Tokens - jwt.io - 온라인에서 JWT 디코딩 및 서명 확인 가능.