: 'JWT'란'인증(authorization)'을 위해 서버에서 발행한 토큰이다. 이 토큰은 JSON 객체로 사용자에 관한 특정한 정보를 포함하고 있다. 이 토큰은 클라이언트 측에서 API를 보낼 때 사용되는 것으로 해당 토큰에 담겨있는 정보와 '인증'이 필요한 유저를 확인하게 된다.
: 'JWT'는 다음과 같은 모습이다.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.XbPfbIHMI6arZ3Y922BhjWgQzWXcXNrz0ogtVhfEd2o
이 코드를 해독하면 'JSON' 객체를 얻게 되는데, 이는 'header', 'payload', 'signature' 이 세 부분으로 구분된다.
'JWT'토큰을 받는 간단한 로그인 과정을 살펴보자.
클라이언트 측에서 'username/password'를 인증을 위한 엔드포인트로 요청을 서버측으로 보내고 그 응답으로 JWT 토큰을 보낸다. 후에 클라이언트 측에서 메모리에 토큰을 저장한다.
먼저 로그인 요청을 보내는 코드를 살펴보자.
//로그인 버튼에 `handleSubmit`이라는 핸들러를 넣어 요청을 보냄.
async function handleSubmit () {
//...
// Make the login API call
const response = await fetch(`/auth/login`, {
method: 'POST',
body: JSON.stringify({ username, password })
})
//...
// Extract the JWT from the response
const { jwt_token } = await response.json()
//...
// Do something the token in the login method
await login({ jwt_token })
}
로그인 API는 응답으로 토큰을 보내고 클라이언트 측에서는 이 토큰을 '/utils/auth'로 login
함수에 넣어준다.
import { login } from '../utils/auth'
await login({ jwt_token })
JWT 토큰을 어딘가에 저장해야 할 때, localStorage(로컬스토리지)가 떠오를지도 모르겠다. 하지만 좋은 생각은 아니다. 이 방법은 XSS 공격에 취약하기 때문이다.
안타깝게도 이 방법 또한 XSS 공격에 취약하다. 클라이언트가 아닌 서버에서 만들어진 HttpOnly
쿠키가 대안으로 떠오를지도 모른다. 하지만 이것은 CSRF 공격에 취약하다. HttpOnly
와 까다로운 CORS 규칙은 CSRF 공격에서 자유로울 수 없으며 쿠키를 사용하는 것은 적합한 CSRF 완화 전략이 필요하다.
일단 메모리에 저장해보자.
let inMemoryToken;
function login ({ jwt_token, jwt_token_expiry }, noRedirect) {
inMemoryToken = {
token: jwt_token,
expiry: jwt_token_expiry
};
if (!noRedirect) {
Router.push('/app')
}
}
보이는 것처럼 '메모리'에 토큰을 저장했다. 물론 유저가 리디렉팅을 하면 토큰이 날아갈 것이다. 이는 나중에 다루도록 하겠다.
header
에 담아서 보낼 수 있음.payload
부분에 있는 데이터를 가져오기 위해 JWT를 복호화하면 됨.const jwt_token = inMemoryToken;
if (!jwt_token) { // 토큰이 없으면
Router.push('/login') // 로그인페이지로 라우팅
}
return jwt_token // 토큰이 있으면 리턴
토큰 변수가 설정되어 있는지, 그렇지 않다면 다시 로그인 페이지로 리디렉팅되게 함으로써 확인할 수 있다.