app-attest-server
v1.0.2
Published
A robust and easy-to-use npm package that provides server-side validation of Apple App Attest artifacts and assertions. Simple toolkit for iOS device attestation.
Maintainers
Readme
app-attest-server: iOS Device Attestation Validation for Node.js
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.
Table of Contents
- Requirements
- Features
- Installation
- Usage
- Configuration
- API Reference
- Examples
- Important Notes
- iOS Client Package
- Finding Your Team ID
- License
Requirements
- Node.js 16.0.0 or higher
- Apple Developer Account with Team ID
- iOS app using App Attest (iOS 14.0+)
Features
- 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
Installation
npm install app-attest-serverThe package automatically sets up the necessary SQLite database and tables to store and manage attestations.
Usage
Basic Server Setup
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');
});Configuration
Required Configuration
- 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
Environment Variables
# 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=3001Database Configuration
- 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
API Reference
Core Functions
getNonce(req)
Generates a challenge nonce for attestation.
registerAttestation(req, teamId, bundleIdentifier, DEV_MODE)
Registers a device attestation and stores the public key.
validateAssertion(req, teamId, bundleIdentifier)
Validates assertion signatures from registered devices.
validateAttestation(attestationNonce, keyId, rawAttestObject, teamId, bundleIdentifier, DEV_MODE)
Validates a new device attestation during registration.
Required Endpoints
Your server needs these three endpoints for App Attest to work:
1. GET /api/nonce
- 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
2. POST /api/registerattestation
- 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
3. POST /api/protectedroute (or your protected routes)
- 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.
Examples
Complete working examples are available in the examples/ directory:
- Server: Express.js server with all required endpoints
Important Notes
Development vs Production Modes
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
General Requirements
- 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
iOS Client Package
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)Finding Your Team ID
Your Apple Team ID is a 10-character alphanumeric identifier required for App Attest validation.
If You Have a Paid Apple Developer Account
- 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
If You Don't Have a Paid Account (Testing Only)
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.
License
MIT License - see LICENSE file for details.
