Firexport블로그

Firebase App Check 오류 해결: 403, App attestation failed, 무효한 토큰

App Check 오류는 두 단계 중 한 곳에서 납니다. 앱이 증명 제공자(Play Integrity, App Attest, reCAPTCHA)로 토큰을 받는 단계이거나, Firebase 서비스가 그 토큰을 확인하는 단계입니다. 앞 단계가 실패하면 뒤 단계도 실패하므로, 서비스 에러만 보지 말고 토큰 교환 에러부터 찾아 플랫폼별 등록값을 고치세요. 에뮬레이터, 시뮬레이터, localhost는 원래 증명을 통과하지 못하니 디버그 토큰을 씁니다.

요약

  • 먼저 실패한 단계를 찾습니다. 토큰 교환이 403으로 끝났다면 제공자 설정 문제이고, 교환 에러 없이 서비스만 거부한다면 토큰이 없거나 무효한 것입니다. 메시지로 가리기
  • 개발 환경에는 디버그 토큰을 씁니다. 에뮬레이터, 시뮬레이터, localhost, 디버그 서명 빌드는 실제 증명을 통과하지 못합니다. 등록하는 법
  • 플랫폼마다 맞춰야 할 값이 있습니다. Android는 서명 인증서의 SHA-256, Apple은 Team ID와 App Attest의 production 환경, 웹은 reCAPTCHA 키의 도메인입니다. 플랫폼별 원인
  • AI Logic은 2026년 11월 2일부터 자동으로 강제됩니다. 2027년 1월 21일까지만 미룰 수 있습니다. 해야 할 일

메시지로 실패한 단계 찾기

메시지실패한 단계
Android: Error returned from API. code: 403 body: App attestation failed.토큰 받기. App Check 서버가 Play Integrity 결과를 받아 주지 않았습니다.
Apple: The server responded with an error … HTTP status code: 403토큰 받기. App Attest나 DeviceCheck 결과를 받아 주지 않았습니다.
웹: HTTP status: 403. (appCheck/fetch-status-error), 이어서 appCheck/throttled토큰 받기. reCAPTCHA 결과를 받아 주지 않았고, SDK는 24시간 동안 다시 시도하지 않습니다.
Cloud Storage: Firebase App Check token is invalid. (웹 SDK에서는 storage/unauthorized-app)토큰 쓰기. 토큰이 없거나 무효합니다.
호출 가능 함수(callable): functions/unauthenticated토큰 쓰기. enforceAppCheck: true인 함수가 거부했습니다. Auth 토큰이 잘못됐을 때와 같은 코드라, 함수 로그의 AppCheck token was rejected로 구분합니다.
Realtime Database: Provided AppCheck credentials for the app named ... are invalid토큰 쓰기. 토큰이 무효합니다.
Firebase AI Logic: 403 PERMISSION_DENIED: ... you must enforce Firebase App Check.토큰 쓰기. AI Logic 절을 보세요.

토큰을 받지 못하면 서비스 요청에는 유효한 토큰이 없습니다(웹 SDK는 자리 표시용 토큰을 붙여 보냅니다). 그러니 서비스 쪽 에러가 보여도 토큰 받기 에러가 함께 찍혀 있는지 먼저 확인하세요. 실패한 뒤에는 SDK가 재시도를 미룹니다. 웹은 403·404 뒤 24시간, Android와 Apple은 400·404 뒤 24시간, 그 밖의 에러는 최대 4시간입니다. 이 기록은 메모리에만 있으니, 설정을 고친 뒤에는 앱을 다시 시작하거나 페이지를 새로고침하고 확인하세요.

개발 중에는 디버그 토큰

에뮬레이터와 시뮬레이터는 유효한 기기로 인정되지 않고, Google Play에 올라가지 않은 디버그 빌드는 Play Integrity를 통과하지 못합니다. reCAPTCHA 키에는 localhost를 넣지 않는 것이 원칙입니다. 이런 환경에서는 디버그 제공자를 켜고 로그에 찍힌 토큰을 콘솔에 등록합니다.

  1. 개발 빌드에서만 디버그 제공자를 켭니다.
    // Android (Kotlin): com.google.firebase:firebase-appcheck-debug
    Firebase.appCheck.installAppCheckProviderFactory(
        if (BuildConfig.DEBUG) DebugAppCheckProviderFactory.getInstance()
        else PlayIntegrityAppCheckProviderFactory.getInstance(),
    )
    
    // iOS (Swift): before FirebaseApp.configure()
    #if DEBUG
    AppCheck.setAppCheckProviderFactory(AppCheckDebugProviderFactory())
    #endif
    
    // Web: before initializeAppCheck(), in development builds only
    self.FIREBASE_APPCHECK_DEBUG_TOKEN = true;
    
    // Flutter (firebase_app_check 0.5+)
    await FirebaseAppCheck.instance.activate(
      providerAndroid: kDebugMode
          ? const AndroidDebugProvider()
          : const AndroidPlayIntegrityProvider(),
      providerApple: kDebugMode
          ? const AppleDebugProvider()
          : const AppleAppAttestWithDeviceCheckFallbackProvider(),
    );
  2. 앱을 실행하고 로그에서 debug token을 찾습니다. 최신 SDK는 Firebase App Check debug token: <UUID> 형식으로 찍습니다(Android는 logcat, Apple은 Xcode 콘솔, 웹은 브라우저 콘솔).
  3. 토큰을 등록합니다. Firebase 콘솔의 App Check에서 앱(Apps) 탭을 열고, 앱의 ⋮ 메뉴에서 디버그 토큰 관리(Manage debug tokens)를 고릅니다. 앱마다 20개까지 등록할 수 있습니다.
  • 토큰은 바뀝니다. Android는 앱 데이터를 지우거나 다시 설치하면, Apple은 다시 설치하면, 웹은 다른 브라우저 프로필을 쓰거나 사이트 데이터를 지우면 새 토큰이 생깁니다. 새 토큰은 다시 등록해야 합니다.
  • CI에서는 등록해 둔 토큰을 넘깁니다. 웹은 self.FIREBASE_APPCHECK_DEBUG_TOKEN에 토큰 문자열을, Android는 계측 테스트 인수 firebaseAppCheckDebugSecret을, Apple은 scheme 환경 변수 AppCheckDebugToken을, Flutter는 debugToken 매개변수를 씁니다.
  • 토큰은 비밀번호처럼 다룹니다. 저장소에 올리지 말고, 디버그 제공자와 토큰을 프로덕션 빌드에 넣지 마세요.

플랫폼별 원인

Android (Play Integrity)

증상원인해결
에뮬레이터나 디버그 빌드에서만 실패에뮬레이터는 유효한 기기가 아니고, 디버그 서명은 Google Play가 모릅니다.디버그 토큰
Play에서 받은 앱이 실패업로드 키의 SHA-256을 등록했습니다. Play 앱 서명을 쓰면 Google이 앱 서명 키로 다시 서명합니다.Play Console의 Play 앱 서명 관리(Manage Play app signing) 페이지에 나오는 앱 서명 키 인증서의 SHA-256을 App Check에 등록
Play 밖에서 배포한 앱이 실패기본 설정은 Google Play가 아는 앱 버전(PLAY_RECOGNIZED)만 통과시킵니다.App Check의 Play Integrity 설정에서 PLAY_RECOGNIZED를 필요 없음(Not required)으로 바꾸고, 최소 기기 무결성을 Device integrity로 올림
사용자가 늘면서 실패(TOO_MANY_REQUESTS, -8)Play Console에 연결되지 않은 Cloud 프로젝트는 Play Integrity 요청이 하루 1만 건으로 묶입니다.Play Console의 Play Integrity API 항목에서 Firebase 프로젝트를 연결(Link Cloud project)하고, 필요하면 할당량 증가 요청. Google Play에 있는 앱만 연결할 수 있습니다.

Apple (App Attest, DeviceCheck)

증상원인해결
시뮬레이터에서 실패시뮬레이터는 유효한 기기가 아닙니다.디버그 토큰
Xcode로 설치한 빌드만 실패App Attest capability를 추가하면 환경이 development로 잡히는데, App Check는 이 환경의 결과를 받지 않습니다. TestFlight와 App Store 빌드는 늘 production입니다.entitlements 파일의 App Attest Environment를 production으로
모든 기기에서 실패Firebase에 등록한 Team ID가 앱의 것과 다릅니다.Firebase 프로젝트 설정의 Apple 앱에 올바른 Team ID 입력
Mac이나 앱 확장에서 실패App Attest를 지원하지 않는 환경입니다.DeviceCheck를 대체 제공자로 붙임. DeviceCheck는 Apple 개발자 사이트에서 만든 키(.p8)와 키 ID를 App Check에 등록
Flutter: 고른 제공자가 실제 기기에서 안 쓰임AppDelegate에서 FirebaseApp.configure()를 먼저 부르면 providerApple이 무시됩니다.AppDelegate의 configure()를 지우고 Dart의 Firebase.initializeApp()만 씀

웹 (reCAPTCHA)

증상원인해결
배포한 사이트에서만 실패그 도메인이 reCAPTCHA 키의 도메인 목록에 없습니다.키 설정에 도메인 추가. 반영까지 최대 30분
localhost에서 실패운영용 키에는 localhost를 넣지 않습니다.디버그 토큰
키를 바꾼 뒤 실패제공자 클래스와 Firebase에 등록한 값의 짝이 맞지 않습니다.Enterprise 키는 코드에 ReCaptchaEnterpriseProvider, Firebase에 사이트 키. v3 키는 코드에 ReCaptchaV3Provider, Firebase에 비밀 키
고쳤는데도 계속 실패403·404 뒤에는 SDK가 24시간 동안 토큰 교환을 다시 시도하지 않습니다.페이지 새로고침. 이 기록은 메모리에만 있습니다.

AI Logic: 2026년 11월 2일부터 자동 강제

Firebase는 2026년 11월 2일부터, App Check를 강제하지 않던 프로젝트의 Firebase AI Logic에 App Check를 자동으로 강제합니다. 유효한 토큰이 없는 요청은 막히고, App Check 없이 배포된 앱 버전도 마찬가지입니다. App Check를 언급하는 AI Logic 에러는 두 가지입니다.

403 PERMISSION_DENIED: To access this model, you must enforce
Firebase App Check.

403 PERMISSION_DENIED: Firebase AI Logic has been deactivated in this
project. To resume using Firebase AI Logic, you must enforce Firebase
App Check.
  • 첫째는 유효한 App Check 토큰 없이 남용이 잦은 모델을 부른 경우입니다. App Check를 설정하고 강제하세요. 개발 중에는 디버그 제공자를 씁니다.
  • 둘째는 프로젝트가 비활성으로 판정됐고 App Check를 강제하지 않은 경우입니다. 이미 강제 중인데도 나온다면 재생 보호(replay protection)를 확인하세요. 켜져 있으면(Monitor only 모드 포함) 제한 사용 토큰(limited-use token)만 인정됩니다.

App Check가 없는 버전이 아직 사용자에게 나가 있다면 강제를 미룰 수 있습니다. App Check의 API(APIs) 탭에서 Firebase AI Logic 행의 Enforces 날짜를 바꾸면 되고, 2027년 1월 21일이 마지막입니다. 이미 강제됨(Enforced)이라면 날짜를 바꾼 뒤 그 행의 설정(Set up)에서 기본 보호(Baseline protection)를 Unenforced (monitoring only)로, 재생 보호를 Disabled로 바꿔야 풀립니다. Google이 권하지 않는 방법이니, 그 전에 App Check를 넣은 버전을 내보내세요.

다른 서비스에 강제하기 전에

  • 지표부터 봅니다. App Check의 API(APIs) 탭은 서비스마다 요청을 확인됨(Verified), 오래된 클라이언트(Outdated client, 토큰 없음), 알 수 없는 출처(Unknown origin), 무효(Invalid)로 나눠 보여 줍니다. 강제하면 확인된 요청만 통과하니, 최근 요청이 거의 다 확인됨일 때 강제하세요.
  • 옛 앱 버전은 막힙니다. App Check SDK가 없는 버전은 강제하는 순간 요청이 거부됩니다.
  • 적용까지 최대 15분이 걸립니다. 강제를 켜자마자 결과를 판단하지 마세요.
  • Cloud Functions는 호출 가능 함수만 강제할 수 있습니다. enforceAppCheck: true를 넣고 다시 배포해야 적용됩니다. onRequest 함수는 App Check 강제 대상이 아니라서 Admin SDK로 토큰을 직접 확인해야 합니다.

출처

전체 글 →