App attestation service that protects your backend resources from abuse. Verifies that requests come from your authentic app.
/**
* Initializes App Check with provider configuration
* @param app - Firebase app instance
* @param options - App Check configuration options
* @returns App Check service instance
*/
function initializeAppCheck(app: FirebaseApp, options: AppCheckOptions): AppCheck;
interface AppCheck {
/** Firebase app instance */
readonly app: FirebaseApp;
/** Whether automatic token refresh is enabled */
readonly isTokenAutoRefreshEnabled: boolean;
}
interface AppCheckOptions {
/** App Check provider instance */
provider: AppCheckProvider;
/** Whether to automatically refresh tokens */
isTokenAutoRefreshEnabled?: boolean;
}/**
* Gets an App Check token
* @param appCheckInstance - App Check service instance
* @param forceRefresh - Whether to force token refresh
* @returns Promise resolving to token result
*/
function getToken(appCheckInstance: AppCheck, forceRefresh?: boolean): Promise<AppCheckTokenResult>;
/**
* Gets a limited-use App Check token
* @param appCheckInstance - App Check service instance
* @returns Promise resolving to token result
*/
function getLimitedUseToken(appCheckInstance: AppCheck): Promise<AppCheckTokenResult>;
/**
* Adds a listener for token changes
* @param appCheckInstance - App Check service instance
* @param observer - Token change observer
* @param onError - Optional error callback
* @param onCompletion - Optional completion callback
* @returns Function to unsubscribe the listener
*/
function onTokenChanged(
appCheckInstance: AppCheck,
observer: PartialObserver<AppCheckTokenResult>,
onError?: (error: Error) => void,
onCompletion?: () => void
): Unsubscribe;
interface AppCheckTokenResult {
/** App Check token */
readonly token: string;
/** Token expiration time in milliseconds */
readonly expireTimeMillis: number;
}
type Unsubscribe = () => void;/**
* reCAPTCHA v3 App Check provider
*/
class ReCaptchaV3Provider implements AppCheckProvider {
/**
* Creates a reCAPTCHA v3 provider
* @param siteKey - reCAPTCHA v3 site key
*/
constructor(siteKey: string);
}
/**
* reCAPTCHA Enterprise App Check provider
*/
class ReCaptchaEnterpriseProvider implements AppCheckProvider {
/**
* Creates a reCAPTCHA Enterprise provider
* @param siteKey - reCAPTCHA Enterprise site key
*/
constructor(siteKey: string);
}
/**
* Custom App Check provider for advanced use cases
*/
abstract class CustomProvider implements AppCheckProvider {
/**
* Gets a token from the custom provider
* @returns Promise resolving to custom token
*/
abstract getToken(): Promise<CustomProviderToken>;
}
interface AppCheckProvider {
/** Provider identifier */
readonly providerId: string;
}
interface CustomProviderToken {
/** Custom token string */
token: string;
/** Token expiration time */
expireTimeMillis: number;
}Usage Examples:
import { initializeApp } from 'firebase/app';
import {
initializeAppCheck,
ReCaptchaV3Provider,
getToken,
onTokenChanged
} from 'firebase/app-check';
const app = initializeApp(firebaseConfig);
// Initialize App Check with reCAPTCHA v3
const appCheck = initializeAppCheck(app, {
provider: new ReCaptchaV3Provider('6LcX...-your-site-key'),
isTokenAutoRefreshEnabled: true
});
// Get App Check token
try {
const tokenResult = await getToken(appCheck);
console.log('App Check token:', tokenResult.token);
console.log('Expires at:', new Date(tokenResult.expireTimeMillis));
} catch (error) {
console.error('Failed to get App Check token:', error);
}
// Listen for token changes
const unsubscribe = onTokenChanged(appCheck, (tokenResult) => {
console.log('New App Check token:', tokenResult.token);
});
// Clean up listener
unsubscribe();import { initializeAppCheck, ReCaptchaEnterpriseProvider } from 'firebase/app-check';
// Initialize with reCAPTCHA Enterprise
const appCheck = initializeAppCheck(app, {
provider: new ReCaptchaEnterpriseProvider('6LcX...-your-enterprise-site-key'),
isTokenAutoRefreshEnabled: true
});
// Enterprise provider offers additional security features
// and better analytics in Google Cloud Consoleimport { initializeAppCheck, CustomProvider } from 'firebase/app-check';
class MyCustomAppCheckProvider extends CustomProvider {
constructor(private apiKey: string) {
super();
}
async getToken(): Promise<CustomProviderToken> {
try {
// Implement your custom token generation logic
const response = await fetch('/api/app-check-token', {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
timestamp: Date.now(),
userAgent: navigator.userAgent,
// Add other attestation data
})
});
if (!response.ok) {
throw new Error('Failed to get custom token');
}
const data = await response.json();
return {
token: data.token,
expireTimeMillis: data.expireTime
};
} catch (error) {
console.error('Custom provider token generation failed:', error);
throw error;
}
}
}
// Use custom provider
const customProvider = new MyCustomAppCheckProvider('your-api-key');
const appCheck = initializeAppCheck(app, {
provider: customProvider,
isTokenAutoRefreshEnabled: true
});class AppCheckTokenManager {
private appCheck: AppCheck;
private tokenCache: AppCheckTokenResult | null = null;
private refreshTimer: NodeJS.Timeout | null = null;
constructor(appCheck: AppCheck) {
this.appCheck = appCheck;
this.setupTokenRefresh();
}
private setupTokenRefresh(): void {
// Listen for token changes
onTokenChanged(this.appCheck, (tokenResult) => {
this.tokenCache = tokenResult;
this.scheduleTokenRefresh(tokenResult);
});
}
private scheduleTokenRefresh(tokenResult: AppCheckTokenResult): void {
if (this.refreshTimer) {
clearTimeout(this.refreshTimer);
}
// Refresh token 5 minutes before expiration
const refreshTime = tokenResult.expireTimeMillis - Date.now() - (5 * 60 * 1000);
if (refreshTime > 0) {
this.refreshTimer = setTimeout(async () => {
try {
await this.refreshToken();
} catch (error) {
console.error('Failed to refresh App Check token:', error);
}
}, refreshTime);
}
}
async getValidToken(): Promise<string> {
if (!this.tokenCache || this.isTokenExpiringSoon(this.tokenCache)) {
await this.refreshToken();
}
return this.tokenCache!.token;
}
private isTokenExpiringSoon(tokenResult: AppCheckTokenResult): boolean {
// Consider token expiring if less than 5 minutes remaining
const timeUntilExpiry = tokenResult.expireTimeMillis - Date.now();
return timeUntilExpiry < (5 * 60 * 1000);
}
private async refreshToken(): Promise<void> {
const tokenResult = await getToken(this.appCheck, true);
this.tokenCache = tokenResult;
}
cleanup(): void {
if (this.refreshTimer) {
clearTimeout(this.refreshTimer);
this.refreshTimer = null;
}
}
}
// Usage
const tokenManager = new AppCheckTokenManager(appCheck);
// Get valid token for API calls
const token = await tokenManager.getValidToken();
// Use token in API requests
const response = await fetch('/api/protected-endpoint', {
headers: {
'X-Firebase-AppCheck': token
}
});
// Clean up when done
tokenManager.cleanup();App Check automatically integrates with other Firebase services:
import { getFirestore, collection, getDocs } from 'firebase/firestore';
import { getFunctions, httpsCallable } from 'firebase/functions';
import { getStorage, ref, uploadBytes } from 'firebase/storage';
// App Check tokens are automatically included in requests
// to Firebase services when App Check is initialized
const db = getFirestore(app);
const functions = getFunctions(app);
const storage = getStorage(app);
// These operations will include App Check tokens automatically
const querySnapshot = await getDocs(collection(db, 'protected-collection'));
const protectedFunction = httpsCallable(functions, 'protectedFunction');
const result = await protectedFunction({ data: 'sensitive' });
const fileRef = ref(storage, 'protected/file.txt');
await uploadBytes(fileRef, fileData);// Integrate App Check with your custom APIs
class SecureApiClient {
private appCheck: AppCheck;
private baseUrl: string;
constructor(appCheck: AppCheck, baseUrl: string) {
this.appCheck = appCheck;
this.baseUrl = baseUrl;
}
private async getHeaders(): Promise<Record<string, string>> {
const tokenResult = await getToken(this.appCheck);
return {
'Content-Type': 'application/json',
'X-Firebase-AppCheck': tokenResult.token,
'Authorization': `Bearer ${await this.getUserToken()}`
};
}
private async getUserToken(): Promise<string> {
// Get user authentication token if needed
const auth = getAuth();
const user = auth.currentUser;
return user ? await user.getIdToken() : '';
}
async secureRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
const headers = await this.getHeaders();
const response = await fetch(`${this.baseUrl}${endpoint}`, {
...options,
headers: {
...headers,
...options.headers
}
});
if (!response.ok) {
if (response.status === 401) {
throw new Error('App Check verification failed');
}
throw new Error(`API request failed: ${response.statusText}`);
}
return response.json();
}
// Specific API methods
async getUserProfile(userId: string): Promise<UserProfile> {
return this.secureRequest(`/api/users/${userId}`);
}
async updateUserData(userId: string, data: Partial<UserProfile>): Promise<void> {
await this.secureRequest(`/api/users/${userId}`, {
method: 'PATCH',
body: JSON.stringify(data)
});
}
}
// Usage
const apiClient = new SecureApiClient(appCheck, 'https://api.myapp.com');
try {
const profile = await apiClient.getUserProfile('user123');
console.log('User profile:', profile);
} catch (error) {
if (error.message.includes('App Check')) {
console.error('App verification failed - possible abuse detected');
} else {
console.error('API error:', error);
}
}// Debug mode for development
if (process.env.NODE_ENV === 'development') {
// Use debug provider for local development
// Note: This requires setting up debug tokens in Firebase Console
(window as any).FIREBASE_APPCHECK_DEBUG_TOKEN = 'debug-token-from-console';
}
// Initialize App Check after setting debug token
const appCheck = initializeAppCheck(app, {
provider: new ReCaptchaV3Provider('your-site-key'),
isTokenAutoRefreshEnabled: true
});
// Test App Check integration
async function testAppCheck(): Promise<void> {
try {
console.log('Testing App Check...');
const tokenResult = await getToken(appCheck);
console.log('✓ App Check token obtained');
// Test with Firebase service
const db = getFirestore(app);
const testQuery = await getDocs(collection(db, 'test'));
console.log('✓ Firestore query with App Check succeeded');
// Test with custom API
const response = await fetch('/api/app-check-test', {
headers: {
'X-Firebase-AppCheck': tokenResult.token
}
});
if (response.ok) {
console.log('✓ Custom API with App Check succeeded');
} else {
console.error('✗ Custom API with App Check failed');
}
} catch (error) {
console.error('✗ App Check test failed:', error);
}
}
// Run test in development
if (process.env.NODE_ENV === 'development') {
testAppCheck();
}// Server-side App Check verification (Node.js example)
const admin = require('firebase-admin');
// Middleware to verify App Check tokens
function verifyAppCheck(req, res, next) {
const appCheckToken = req.headers['x-firebase-appcheck'];
if (!appCheckToken) {
return res.status(401).json({ error: 'App Check token required' });
}
admin.appCheck().verifyToken(appCheckToken)
.then((appCheckClaims) => {
// Token is valid
req.appCheckClaims = appCheckClaims;
next();
})
.catch((error) => {
console.error('App Check verification failed:', error);
res.status(401).json({ error: 'Invalid App Check token' });
});
}
// Use middleware on protected routes
app.get('/api/protected-data', verifyAppCheck, (req, res) => {
// This endpoint is protected by App Check
res.json({ data: 'sensitive information' });
});// Monitor App Check failures
class AppCheckMonitor {
private failureCount = 0;
private lastFailureTime = 0;
async monitoredGetToken(appCheck: AppCheck): Promise<AppCheckTokenResult> {
try {
const tokenResult = await getToken(appCheck);
this.resetFailureCount();
return tokenResult;
} catch (error) {
this.recordFailure();
if (this.shouldTriggerAlert()) {
this.sendAlert(error);
}
throw error;
}
}
private recordFailure(): void {
this.failureCount++;
this.lastFailureTime = Date.now();
}
private resetFailureCount(): void {
this.failureCount = 0;
}
private shouldTriggerAlert(): boolean {
// Trigger alert if 5 failures in 10 minutes
const tenMinutesAgo = Date.now() - (10 * 60 * 1000);
return this.failureCount >= 5 && this.lastFailureTime > tenMinutesAgo;
}
private sendAlert(error: Error): void {
console.error('App Check failure threshold exceeded:', error);
// Send to monitoring service
fetch('/api/alerts', {
method: 'POST',
body: JSON.stringify({
type: 'app_check_failure',
count: this.failureCount,
error: error.message,
timestamp: Date.now()
})
});
}
}