Development

JWT토큰을 사용한 안전한 로그인 구현

JWT (JSON Web Tokens)는 인터넷 표준인 RFC 7519에 정의된, 두 개체 사이에서 JSON 객체를 사용하여 안전하게 정보를 교환하기 위한 컴팩트하고 자가 수용적인(self-contained) 방식입니다. JWT는 클라이언트와 서버 사이의 인증 및 정보 교환에 널리 사용되며, 특히 웹 애플리케이션과 모바일 애플리케이션에서 RESTful API의 보안을 관리하는 데 유용합니다.

JWT Token는 3가지로 구분됩니다.

  1. 헤더(Header): 토큰의 타입(보통 JWT)과 사용된 해시 알고리즘(예: HMAC SHA256 또는 RSA)을 지정하는 JSON 객체입니다. 
  2. 페이로드(Payload): 토큰에 포함할 클레임(claims)을 담는 JSON 객체입니다. 클레임은 토큰에 대한 정보를 제공하는 속성이며, 세 가지 유형(등록된, 공개, 비공개 클레임)이 있습니다. 등록된 클레임은 서비스에서 필요로 하는 정보(예: 사용자 식별 정보, 발급자, 만료 시간 등)를 제공하고, 공개 및 비공개 클레임은 사용자 정의 정보를 포함할 수 있습니다.
    1. 등록된(Registered) Claims: JWT 표준에서 사전 정의한 일련의 클레임들로, 선택적으로 사용할 수 있습니다. 이들은 토큰에 대한 일반적인 정보를 제공하며, 모든 등록된 클레임들은 이름이 세 글자로 된 짧은 형태입니다. 예를 들어:
      1. iss : 토큰을 발급한 발급자.
      2. sub (Subject): 토큰의 주제(또는 사용자).
      3. aud (Audience): 토큰의 수신자.
      4. exp (Expiration Time): 토큰의 만료 시간.
      5. nbf (Not Before): 토큰이 활성화되기 전의 시간.
      6. iat (Issued At): 토큰이 발급된 시간.
      7. jti (JWT ID): 토큰의 고유 식별자.
    2. 공개(Public) Claims: 이 클레임들은 충돌을 방지하기 위해 IANA JSON Web Token Registry에 등록되거나, URI 형식을 사용하여 정의될 수 있습니다. 공개 클레임들은 사용자 정의 정보를 포함할 수 있으며, 공개적으로 사용되는 클레임 이름을 중복 사용하지 않기 위한 목적으로 등록되거나 정의됩니다.
    3. 비공개(Private) Claims: 두 개체 사이의 합의 하에 사용되는 클레임들로, 사용자 정의 데이터를 전달하는 데 사용됩니다. 이 클레임들은 특정 애플리케이션 또는 조직 내에서만 의미가 있으며, 고유한 이름을 사용하여 충돌이 발생하지 않도록 해야 합니다. 사용자의 ID, 역할(role) 또는 이메일과 같은 정보를 비공개 클레임으로 토큰에 포함시켜, API 요청 시 해당 정보를 서버에서 쉽게 추출하고 검증할 수 있습니다.
  1. 서명(Signature): 헤더의 인코딩된 값과 페이로드의 인코딩된 값을 합친 후, 제공된 비밀키로 해시하여 생성됩니다. 서명은 메시지의 무결성과 정보의 검증을 보장하기 위해 사용됩니다.

 

이 세 부분은 각각 Base64URL로 인코딩되어 .으로 구분되어 연결된 문자열 형태로 제공됩니다. 예를 들어, JWT는 다음과 같이 보일 수 있습니다:

localStorage에 저장되어있는 accessToken을 사용해 정보를 수집하며, accessToken이 만료되었을 때 refreshToken으로 accessToken을 생성해 사용자가 번거롭게 로그인을 하지 않도록 합니다.

 

구현.

  1. 로그인하여 토큰을 발급받는 경우

const accessToken = await JWT.sign(

{ email },

process.env.ACCESS_TOKEN_SECRET,

{

expiresIn: “10s”,

}

);

 

// Refresh token

const refreshToken = await JWT.sign(

{ email },

process.env.REFRESH_TOKEN_SECRET,

{

expiresIn: “1m”,

}

);

 

// Set refersh token in refreshTokens array

refreshTokens.push(refreshToken); //db에 refreshToken을 저장함.

 

res.json({

accessToken,

refreshToken,

});

 

  1. Client: accessToken과 refreshToken을 localstorage에 저장함.
  2. 회원 인증이 필요한 경우 route에 미들웨어로 auth함수를 추가함.

router.get(“/private”, authToken, (req, res) => {

res.json(privatePosts);

});

  1. authToken: req에 user의 email을 얹어주고 다음으로 넘어갈 수 있게 해줌.

const token = req.header(“x-auth-token”);

try {

const user = await jwt.verify(token, process.env.ACCESS_TOKEN_SECRET);

req.user = user.email;

next();

}

 

5. accessToken만료 시 refreshToken으로 accessToken생성(refreshToken은 DB에 저장되어있어야함)

const refreshToken = req.header(“x-auth-token”);

try {

const user = await JWT.verify(

refreshToken,

process.env.REFRESH_TOKEN_SECRET

);

const { email } = user;

const accessToken = await JWT.sign(

{ email },

process.env.ACCESS_TOKEN_SECRET,

{ expiresIn: “10s” }

);

res.json({ accessToken });

}

 

 

Scroll to Top