A lightweight and secure npm package for server-side validation of Apple App Attest artifacts and assertions. Provides robust protection against replay attacks, request forgery, and unauthorized API access by ensuring requests come from genuine, attested iOS devices.
Works seamlessly with AppAttestKit, a Swift package that handles the iOS client-side implementation.
- Requirements
- Features
- Installation
- Usage
- Configuration
- API Reference
- Examples
- Important Notes
- iOS Client Package
- Finding Your Team ID
- License
- Node.js 16.0.0 or higher
- Apple Developer Account with Team ID
- iOS app using App Attest (iOS 14.0+)
- Secure Attestation: Validates App Attest requests to ensure requests come from genuine iOS devices
- Easy Integration: Straightforward API for verifying App Attest requests on the server-side
- Automatic Storage: Uses SQLite to automatically store and manage attestations
npm install app-attest-serverThe package automatically sets up the necessary SQLite database and tables to store and manage attestations.
const express = require('express');
const AppAttest = require('app-attest-server');
// Configuration - use environment variables in production
const teamId = process.env.APPLE_TEAM_ID || 'YOUR_10_CHAR_TEAM_ID';
const bundleIdentifier = process.env.BUNDLE_IDENTIFIER || 'com.example.your-app';
const isDevelopment = process.env.NODE_ENV !== 'production';
const app = express();
app.use(express.json());
// Generate challenge nonce for attestation
app.get('/api/nonce', async (req, res) => {
const randomNonce = await AppAttest.getNonce(req);
if (randomNonce?.error) {
return res.status(400).json({ error: 'Failed to generate nonce' });
}
return res.status(200).json({ nonce: randomNonce });
});
// Register device attestation
app.post('/api/registerattestation', async (req, res) => {
const result = await AppAttest.registerAttestation(req, teamId, bundleIdentifier, isDevelopment);
if (result?.error) {
return res.status(400).json({ error: 'Attestation registration failed' });
}
console.log('Successfully registered attestation');
return res.status(200).json({ success: true });
});
// Protected route with assertion validation
app.post('/api/protectedroute', async (req, res) => {
const assertionResult = await AppAttest.validateAssertion(req, teamId, bundleIdentifier);
if (assertionResult?.error) {
// Important: Return 422 for 'nokey' to trigger client re-attestation
if (assertionResult.error === 'nokey') {
return res.status(422).json({ error: 'Device not registered. Please re-attest.' });
}
return res.status(400).json({ error: 'Assertion validation failed' });
}
console.log(`Assertion validated. Count: ${assertionResult}`);
return res.status(200).json({ assertionsCount: assertionResult });
});
app.listen(3001, () => {
console.log('AppAttestKit server running on port 3001');
});- Team ID: Your 10-character Apple Developer Team ID (see Finding Your Team ID)
- Bundle Identifier: Your app's bundle identifier (e.g., com.example.app)
- DEV_MODE: Set to
truefor development builds,falsefor production
# Required
APPLE_TEAM_ID=YOUR_10_CHAR_TEAM_ID
BUNDLE_IDENTIFIER=com.example.your-app
# Optional
NODE_ENV=production
ATTESTATION_DB_PATH=./attestations.db
PORT=3001- Database Path: Configurable via
ATTESTATION_DB_PATHenvironment variable - Auto-initialization: SQLite database and tables created automatically
- Storage: Device attestation keys, counters, and timestamps
- No manual management: Package handles all database operations
Generates a challenge nonce for attestation.
Registers a device attestation and stores the public key.
Validates assertion signatures from registered devices.
Validates a new device attestation during registration.
Your server needs these three endpoints for App Attest to work:
- Returns a challenge (nonce) to the client for attestation
- Uses
AppAttest.getNonce(req)to generate a secure nonce - Requires
device-idheader from client - Returns 400 if nonce generation fails
- Accepts attestation registration from the iOS client
- Expects request body with
keyIdandattestationObject - Uses
AppAttest.registerAttestation(req, teamId, bundleIdentifier, DEV_MODE) - Returns 400 for validation errors, 200 for success
- Validates assertions on protected routes
- Uses
AppAttest.validateAssertion(req, teamId, bundleIdentifier) - Important: Returns 422 when
error === 'nokey'to trigger client re-attestation - Returns 400 for other validation errors, 200 for success
Note: You can rename these endpoints to match your API design, but the functionality must remain the same.
Complete working examples are available in the examples/ directory:
- Server: Express.js server with all required endpoints
App Attest operates in two distinct environments:
-
Development Mode: Set
DEV_MODE = truefor development builds and testing- Uses AAGUID value "appattestdevelop"
- Attestations don't affect app risk metrics
- Used for simulator testing and development builds
-
Production Mode: Set
DEV_MODE = falsefor App Store builds- Uses AAGUID value "appattest" followed by seven null bytes
- All attestations count toward app risk metrics
- Used for TestFlight, App Store, and enterprise builds
- App Attest requires iOS 14.0+ and real devices (not simulator)
- Always validate the 422 status code to trigger re-attestation
- Store your Team ID and Bundle Identifier securely
Complete the App Attest integration with our iOS client package:
AppAttestKit - Swift Package for iOS App Attest client-side implementation
- Automatic device attestation and assertion generation
- Seamless integration with this server package
- Built-in retry logic and error handling
- Keychain-based secure key storage
// iOS Client Example
import AppAttestKit
let service = AppAttestService(baseUrl: "https://your-server.com")
let (data, response) = try await service.sendSignedRequest(request: request, userId: userId)Your Apple Team ID is a 10-character alphanumeric identifier required for App Attest validation.
- Go to Apple Developer Portal
- Sign in with your Apple ID
- Navigate to Membership section
- Your Team ID is displayed prominently on the membership page
For testing purposes only, you can find your personal Team ID:
- Press F4 (Spotlight) and search for Keychain Access
- Open Keychain Access application
- In the left sidebar, click on login under "Default Keychains"
- Look through the list for an entry called Apple Development
- Find the entry that shows your iCloud email address
- The 10-character Team ID will be listed next to your email
Note: This method is only for testing. For production apps, you need a paid Apple Developer account.
MIT License - see LICENSE file for details.
