A tutorial project demonstrating Apple's DeviceCheck API for device-based banning. This allows you to ban devices (not just accounts) so even if a banned user creates a new account, they cannot access your service.
iOS App Your Server Apple Server
│ │ │
│ 1. Generate device token │ │
│ ─────────────────────────>│ │
│ │ 2. Query/Update bits │
│ │ ──────────────────────────>│
│ │ │
│ │ 3. Return bit values │
│ │ <──────────────────────────│
│ 4. Banned/Not banned │ │
│ <─────────────────────────│ │
Apple stores 2 bits per device that persist even if the app is deleted:
bit0: We use this for "is banned" (true = banned)bit1: Reserved for future use
DeviceCheck/
├── DeviceCheckProj/ # iOS SwiftUI App
│ └── DeviceCheckProj/
│ ├── ContentView.swift # Demo UI
│ ├── DeviceCheckService.swift # DCDevice wrapper
│ └── APIService.swift # Network client
│
└── server/ # Node.js Backend
├── src/
│ ├── index.js # Express server
│ ├── config/database.js # MySQL connection
│ ├── routes/deviceCheck.js # API endpoints
│ ├── services/appleDeviceCheck.js # Apple API
│ └── utils/jwt.js # JWT generation
└── sql/schema.sql # Database schema
- macOS with Xcode 15+
- Node.js 18+
- MySQL 8.0+
- Apple Developer Account with DeviceCheck capability
- Go to App Store Connect
- Navigate to Users and Access > Integrations > Keys
- Click Generate API Key or use the "DeviceCheck" section
- Download the
.p8file (you can only download it once!) - Note your Team ID and Key ID
# Start MySQL if not running
mysql.server start
# Create the database and tables
mysql -u root -p < server/sql/schema.sqlcd server
# Copy environment template
cp .env.example .env
# Edit .env with your values:
# - APPLE_TEAM_ID: Your team ID from App Store Connect
# - APPLE_KEY_ID: Your key ID from the .p8 file name
# - APPLE_PRIVATE_KEY_PATH: Path to your .p8 file
# - DB_PASSWORD: Your MySQL password
# Install dependencies
npm install
# Start the server
npm run dev- If testing on a real device, update the server URL in
APIService.swift:private let baseURL = "http://YOUR_MAC_IP:3000/api"
- Build and run on a real device (DeviceCheck doesn't work on simulator)
| Endpoint | Method | Description |
|---|---|---|
/api/check-device |
POST | Check if a device is banned |
/api/ban-device |
POST | Ban a device |
/api/unban-device |
POST | Unban a device |
/api/register |
POST | Simulate registration (fails if banned) |
/health |
GET | Health check |
# Check device status
curl -X POST http://localhost:3000/api/check-device \
-H "Content-Type: application/json" \
-d '{"deviceToken": "BASE64_TOKEN_HERE"}'
# Ban a device
curl -X POST http://localhost:3000/api/ban-device \
-H "Content-Type: application/json" \
-d '{"deviceToken": "BASE64_TOKEN_HERE", "reason": "Fraudulent activity"}'
# Simulate registration
curl -X POST http://localhost:3000/api/register \
-H "Content-Type: application/json" \
-d '{"deviceToken": "BASE64_TOKEN_HERE", "username": "newuser123"}'- DeviceCheck only works on real devices, not simulators
- Device tokens are ephemeral (temporary) - generate a new one for each request
- Use
api.development.devicecheck.apple.comfor testing - Use
api.devicecheck.apple.comfor production