문제 해결
expo-crypto-dpop 사용 중 발생할 수 있는 오류와 해결 방법
문제 해결
오류 코드별 해결 방법
KEY_GENERATION_FAILED — 키 생성 실패
원인:
- 네이티브 암호화 모듈 초기화 실패
- 기기 보안 정책으로 인한 키 생성 차단 (예: 루팅/탈옥 기기)
expo-secure-store접근 권한 부재
해결 방법:
import ExpoCryptoDpop, { DpopError, DpopErrorCode } from 'expo-crypto-dpop';try {await ExpoCryptoDpop.generateKeyPair();} catch (error) {if (error instanceof DpopError && error.code === DpopErrorCode.KEY_GENERATION_FAILED) {// 1. 한 번 더 시도try {await ExpoCryptoDpop.generateKeyPair();} catch {// 2. 사용자에게 기기 보안 설정 확인 안내Alert.alert('보안 키 생성 실패','기기의 보안 설정을 확인해 주세요. 루팅 또는 탈옥된 기기에서는 지원되지 않을 수 있습니다.');}}}
KEY_NOT_FOUND — 키를 찾을 수 없음
원인:
- 앱 재설치 후 키가 삭제된 상태
deleteKeyPair()가 명시적으로 호출된 경우expo-secure-store데이터 손상
해결 방법:
createProof() 대신 ensureKeyPair()를 먼저 호출하거나, createProof()만 사용하면 내부적으로 자동 처리됩니다. createProof()는 내부에서 ensureKeyPair()를 호출하므로 일반적으로 KEY_NOT_FOUND가 발생하지 않습니다.
만약 직접 getPublicKeyJwk()나 hasKeyPair()를 사용하는 경우:
const hasKey = await ExpoCryptoDpop.hasKeyPair();if (!hasKey) {await ExpoCryptoDpop.ensureKeyPair(); // 자동으로 생성}
SIGNING_FAILED — JWT 서명 실패
원인:
- 저장된 개인 키 JWK가 손상됨
- 네이티브 서명 모듈 오류
해결 방법:
import ExpoCryptoDpop, { DpopError, DpopErrorCode } from 'expo-crypto-dpop';try {const proof = await ExpoCryptoDpop.createProof({ htm: 'GET', htu: '...' });} catch (error) {if (error instanceof DpopError && error.code === DpopErrorCode.SIGNING_FAILED) {// 키 쌍을 삭제하고 재생성await ExpoCryptoDpop.deleteKeyPair();await ExpoCryptoDpop.generateKeyPair();// 재시도const proof = await ExpoCryptoDpop.createProof({ htm: 'GET', htu: '...' });}}
INVALID_INPUT — 잘못된 입력값
원인:
htm또는htu가 빈 문자열이거나 누락됨
해결 방법:
// 잘못된 예await ExpoCryptoDpop.createProof({ htm: '', htu: '' }); // INVALID_INPUT 발생// 올바른 예await ExpoCryptoDpop.createProof({htm: 'GET', // HTTP 메서드 (대소문자 무관)htu: 'https://api.example.com/resource', // 완전한 URI (쿼리 파라미터 제외)});
KEY_DELETION_FAILED — 키 삭제 실패
원인:
expo-secure-store접근 오류- 기기 잠금 상태에서 삭제 시도
해결 방법:
기기가 잠금 해제된 상태에서 삭제를 시도하고 있는지 확인합니다.
try {await ExpoCryptoDpop.deleteKeyPair();} catch (error) {// 삭제 실패 시 앱 재시작 후 재시도 안내console.warn('키 삭제 실패:', error);}
앱 재설치 관련 문제
증상: 앱을 삭제하고 재설치했는데 서버가 DPoP 검증을 실패함
원인: iOS Keychain은 앱 삭제 후에도 데이터를 유지합니다. 재설치 후 이전 키가 서버에 등록되지 않은 새 키와 충돌할 수 있습니다.
해결 방법: ensureKeyPair()를 사용하면 재설치 시 자동으로 처리됩니다. createProof()도 내부적으로 ensureKeyPair()를 호출하므로 별도 처리가 필요하지 않습니다.
재설치 감지가 제대로 동작하는지 확인하려면:
// 디버그용: 네이티브 모듈에서 직접 확인import ExpoCryptoDpopModule from 'expo-crypto-dpop/build/ExpoCryptoDpopModule';const isFirst = await ExpoCryptoDpopModule.checkFirstLaunch();console.log('최초 실행/재설치:', isFirst);
키 교체(Key Rotation)
서버 정책이나 보안 이벤트로 인해 DPoP 키를 교체해야 하는 경우:
async function rotateDpopKey() {try {// 1. 새 키 생성 (기존 키 덮어씀)const newPublicKey = await ExpoCryptoDpop.generateKeyPair();console.log('새 공개 키 생성:', newPublicKey);// 2. 필요 시 서버에 새 공개 키 등록await apiClient.post('/auth/dpop-key', { jwk: newPublicKey });return newPublicKey;} catch (error) {console.error('키 교체 실패:', error);throw error;}}
키 교체 후에는 기존 액세스 토큰이 이전 공개 키에 바인딩되어 있으므로 토큰도 재발급해야 할 수 있습니다. 서버 정책을 확인하세요.
플랫폼별 이슈
iOS
Keychain 공유 오류
expo-secure-store가 Keychain에 접근하지 못하는 경우, Xcode 프로젝트의 Signing & Capabilities 탭에서 Keychain Sharing 기능이 추가되어 있는지 확인합니다.
시뮬레이터에서 SecureStore 오류
iOS 시뮬레이터에서는 Keychain이 제한적으로 동작할 수 있습니다. 실제 기기에서 테스트를 권장합니다.
Android
minSdkVersion 오류
Error: expo-secure-store requires minSdkVersion 23 or higher
android/build.gradle에서 minSdkVersion을 26 이상으로 설정합니다.
루팅된 기기
루팅된 Android 기기에서는 Keystore 접근이 제한될 수 있습니다. 보안 정책에 따라 루팅 기기 사용을 차단하는 것을 고려하세요.
디버깅 팁
현재 키 상태 확인
async function debugKeyState() {const hasKey = await ExpoCryptoDpop.hasKeyPair();const publicKey = await ExpoCryptoDpop.getPublicKeyJwk();console.log('키 존재 여부:', hasKey);console.log('공개 키:', JSON.stringify(publicKey, null, 2));}
DPoP JWT 내용 확인
생성된 DPoP JWT를 디코딩하여 내용을 확인합니다.
const proof = await ExpoCryptoDpop.createProof({htm: 'GET',htu: 'https://api.example.com/test',});// JWT 파싱 (헤더.페이로드.서명)const [headerB64, payloadB64] = proof.split('.');const header = JSON.parse(atob(headerB64.replace(/-/g, '+').replace(/_/g, '/')));const payload = JSON.parse(atob(payloadB64.replace(/-/g, '+').replace(/_/g, '/')));console.log('DPoP JWT 헤더:', header);console.log('DPoP JWT 페이로드:', payload);
jwt.io에서 생성된 DPoP JWT를 붙여넣어 내용을 시각적으로 확인할 수 있습니다. 단, 개발 환경에서만 사용하세요.