React Native 애플리케이션을 개발하다 보면 JWT(JSON Web Token)를 사용하여 사용자 인증을 구현하는 경우가 많습니다. 이 과정에서 토큰을 디코딩하여 사용자 ID나 권한 같은 정보를 추출해야 할 때가 있는데, 이때 몇 가지 문제가 발생할 수 있습니다.
특히 웹 환경에서는 문제없이 작동하던 JWT 라이브러리가 React Native 환경에서는 오류를 발생시키는 경우가 종종 있습니다. 이번 포스팅에서는 React Native에서 JWT 토큰 디코딩 중 발생하는 문제와 그 해결 방법에 대해 알아보겠습니다.
React Native 프로젝트에서 로그인 후 JWT 토큰을 디코딩하려고 할 때 다음과 같은 오류가 발생했습니다:
Error decoding token: [InvalidTokenError: Invalid token specified: invalid base64 for part #2 (Can't find variable: atob)]
이 오류는 jwt-decode
라이브러리가 브라우저 환경의 atob
함수에 의존하고 있는데, React Native 환경에서는 이 함수가 기본적으로 제공되지 않아 발생하는 문제입니다.
문제의 원인을 살펴보면:
환경 차이: 브라우저에서는 atob
와 btoa
함수가 Base64 인코딩/디코딩을 위해 내장되어 있지만, React Native는 이를 제공하지 않습니다.
라이브러리 의존성: jwt-decode
와 같은 많은 JWT 관련 라이브러리들이 웹 환경을 전제로 개발되어 있어, 브라우저 API에 의존합니다.
JWT 토큰 디코딩 함수를 직접 구현하는 방법입니다:
import { DecodedToken } from "./types";
import Base64 from "react-native-base64";
export const decodeToken = (token: string): DecodedToken | null => {
try {
if (!token || token.split(".").length !== 3) {
console.error("Invalid token format");
return null;
}
const base64Payload = token.split(".")[1];
const normalizedPayload = base64Payload
.replace(/-/g, "+")
.replace(/_/g, "/");
const decodedPayload = Base64.decode(normalizedPayload);
return JSON.parse(decodedPayload);
} catch (error) {
console.error("Error decoding token:", error);
return null;
}
};
이 방법의 장점은 외부 라이브러리 의존성을 줄이고, React Native 환경에 최적화된 방식으로 디코딩을 처리할 수 있다는 것입니다.
atob
함수를 React Native 환경에 추가하는 방법입니다:
import { DecodedToken } from "./types";
import { jwtDecode } from "jwt-decode";
import { decode as base64Decode } from 'base-64';
// atob polyfill
global.atob = base64Decode;
export const decodeToken = (token: string): DecodedToken | null => {
try {
if (!token) {
return null;
}
return jwtDecode<DecodedToken>(token);
} catch (error) {
console.error("Error decoding token:", error);
return null;
}
};
이 방법을 사용하려면 base-64
패키지를 설치해야 합니다:
npm install base-64
# 또는
yarn add base-64
React Native 환경을 위해 특별히 설계된 JWT 라이브러리를 사용하는 것도 좋은 방법입니다. 예를 들어, react-native-jwt-decode
와 같은 라이브러리가 있습니다.
어떤 방법을 선택하든, 오류 처리를 강화하여 앱의 안정성을 높이는 것이 중요합니다:
export const decodeToken = (token: string): DecodedToken | null => {
try {
if (!token) {
console.error("Token is null or undefined");
return null;
}
// 디코딩 로직...
} catch (error) {
// 자세한 오류 로깅
console.error("Error decoding token:", error);
// 앱이 중단되지 않도록 null 반환
return null;
}
};
토큰 디코딩이 실패하더라도 앱의 주요 기능은 계속 작동할 수 있도록 해야 합니다. 다음은 이를 처리하는 방법의 예시입니다:
const handleLoginEvent = async (data: LoginEventData) => {
try {
const { accessToken, refreshToken, jti, navigation, shouldNavigate = true } = data;
// 토큰 디코딩 시도
let userId = "";
try {
const decodedToken = decodeToken(accessToken);
userId = decodedToken?.userId?.toString() ?? "";
} catch (decodeError) {
console.error("Token decoding failed, continuing without userId:", decodeError);
// 디코딩 실패해도 계속 진행
}
// AsyncStorage에 데이터 저장
await AsyncStorage.multiSet([
["jti", jti],
["accessToken", accessToken],
["refreshToken", refreshToken],
["isLoggedIn", "true"],
["userId", userId],
]);
// 네비게이션 처리
if (shouldNavigate && navigation) {
navigation.reset({
index: 0,
routes: [{ name: "MainTab" }],
});
}
} catch (error) {
console.error("로그인 처리 실패:", error);
}
};
가능하다면, 토큰 디코딩에 의존하지 않고 API 응답에서 직접 사용자 ID 등의 정보를 받아오는 것도 좋은 방법입니다:
// 로그인 API 응답
interface LoginResponse {
accessToken: string;
refreshToken: string;
jti: string;
userId: number; // 백엔드에서 추가로 제공
}
React Native 환경에서 JWT 토큰 디코딩은 웹 환경과 다른 접근 방식이 필요합니다. 직접 구현, polyfill 사용, 또는 전용 라이브러리 사용 등 상황에 맞는 최적의 방법을 선택하면 됩니다.
중요한 점은 토큰 디코딩 실패가 앱의 전체 기능에 영향을 미치지 않도록 견고한 오류 처리를 구현하는 것입니다. 이를 통해 사용자는 원활한 앱 경험을 유지할 수 있습니다.