React Native로 앱을 개발하던 중 흥미로운 상황에 직면했습니다.
매칭 목록 화면에서 사용자가 자신이 만든 매칭을 구분할 수 있도록 하는 기능을 구현하고 있었는데, 각 매칭의 hostId와 현재 로그인한 사용자의 ID를 비교해야 했습니다.
문제는 로그인 API 응답에서 사용자 ID를 직접 받지 못하고 있었죠.
응답으로는 jti, accessToken, refreshToken만 받고 있어서 현재 사용자의 ID를 알 수 없었습니다.
이 문제를 해결하기 위해 백엔드 개발자와 논의하던 중, JWT(JSON Web Token)에 대해 자세히 알게 되었습니다.
JWT는 당사자 간에 정보를 JSON 객체로 안전하게 전송하기 위한 독립적인 방식입니다.
이 정보는 디지털 서명이 되어 있기 때문에 신뢰할 수 있습니다.
JWT는 세 부분으로 구성됩니다:
Header (헤더)
{
"alg": "HS256", // 서명 알고리즘
"typ": "JWT" // 토큰 유형
}
Payload (내용)
{
"sub": "user@email.com", // 사용자 이메일
"userId": 123, // 사용자 ID
"name": "John Doe", // 사용자 이름
"iat": 1516239022, // 발행 시간
"exp": 1516242622 // 만료 시간
}
Signature (서명)
이 세 부분은 각각 Base64로 인코딩되어 점(.)으로 구분됩니다:
xxxxx.yyyyy.zzzzz
Header.Payload.Signature
Payload에 포함되는 주요 클레임들의 의미를 살펴보겠습니다:
jti (JWT ID)
iat (Issued At)
exp (Expiration Time)
사용자가 로그인을 성공하면 서버는 JWT를 생성합니다.
서버는 비밀 키를 사용하여 토큰에 서명합니다.
서버는 이 토큰을 클라이언트에게 전달합니다.
클라이언트는 이후의 요청에서 이 토큰을 헤더에 포함시켜 전송합니다.
서버는 토큰의 서명을 확인하고 요청을 처리합니다.
로그인 과정
// 서버 응답 예시
{
jti: "1cc00dd7-75a3-41c6-9d8f-1aa150821d6b",
accessToken: "eyJhbG...",
refreshToken: "eyJhbG..."
}
await AsyncStorage.multiSet([
["jti", response.data.jti],
["accessToken", response.data.accessToken],
["refreshToken", response.data.refreshToken],
["isLoggedIn", "true"],
]);
const decodeToken = (token: string) => {
try {
const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = decodeURIComponent(atob(base64).split('').map(c =>
'%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
).join(''));
return JSON.parse(jsonPayload);
} catch (error) {
console.error('Error decoding token:', error);
return null;
}
};
상태 비저장(Stateless)
자가 수용적(Self-contained)
보안성
확장성
userId를 JWT payload에 포함시켜 전송하기로 결정했습니다:
// 사용 예시
const getUserId = async () => {
const token = await AsyncStorage.getItem('accessToken');
if (!token) return null;
const decoded = decodeToken(token);
return decoded?.userId;
};
accessToken은 보통 짧은 만료 시간(예: 1시간)
refreshToken은 긴 만료 시간(예: 2주)
토큰 크기는 일반적으로 1KB 이하로 유지하는 것이 좋음
민감한 정보의 예: 비밀번호, 개인식별정보, 금융정보 등
JWT는 단순히 인증을 위한 도구를 넘어 클라이언트-서버 간 안전한 정보 전달의 수단이 될 수 있습니다.
이번 경험이 좋은 예시인데, 처음에는 단순히 사용자 ID를 API 응답에 추가해달라고 요청하려 했지만, JWT의 payload를 활용하는 방식을 선택함으로써 더 깔끔하고 확장 가능한 솔루션을 구현할 수 있었습니다.
특히 JWT의 다양한 구성 요소들이 각자의 역할을 수행하는 것이 인상적이었습니다.
payload를 통한 데이터 전달뿐만 아니라, jti를 통한 세션 관리, 만료 시간을 통한 보안 강화 등 각 요소들이 서로 조화롭게 동작하면서 안전하고 효율적인 인증 시스템을 구축할 수 있었죠.
이러한 경험을 통해 프론트엔드 개발자로서 인증 시스템에 대한 이해도를 한층 높일 수 있었고, 백엔드 개발자와의 협업 시 더 나은 의사소통을 할 수 있게 되었습니다.
때로는 당면한 문제를 해결하기 위해 새로운 기술을 배우는 과정이, 결과적으로 더 나은 해결책을 찾는 길이 될 수 있다는 것을 깨달은 소중한 경험이었습니다.