apple-rs
A Rust library for Apple Sign-In authentication, CloudKit Web Services, and the App Store Server API.
Features
- Apple Sign-In — Validate authorization codes, refresh tokens, generate authorization URLs, and parse user info from JWT ID tokens.
- CloudKit Web Services — Full CRUD for records, zones, subscriptions, change tracking, user discovery, asset uploads, APNs token management, and push notification parsing.
- App Store Server API — Transaction history, subscription management, consumption reporting, refund lookup, server notification handling (V1 & V2), JWS signed data verification with X.509 chain validation, and retention messaging.
- Shared key management — A single ECDSA P-256 key pair can be shared across Sign-In, CloudKit, and App Store.
- Async/await — All network operations are async.
Cargo Features
| Feature | Default | Description |
|---|---|---|
auth |
Yes | Apple Sign-In authentication |
cloudkit |
Yes | CloudKit Web Services (adds sha2, chrono) |
appstore |
No | App Store Server API (adds chrono, x509-cert) |
[]
= "0.2.0"
# Or pick features:
# apple = { version = "0.2.0", default-features = false, features = ["auth"] }
# apple = { version = "0.2.0", default-features = false, features = ["cloudkit"] }
# apple = { version = "0.2.0", features = ["appstore"] }
Apple Sign-In
Initializing the Auth Client
use ;
// From a .p8 file
let auth = new?;
// Or from a base64-encoded key
let auth = new_b64?;
Validating an Authorization Code
let token_response = auth.validate_code.await?;
println!;
Validating with a Redirect URI
let token_response = auth.validate_code_with_redirect_uri.await?;
Refreshing a Token
let token_response = auth.validate_refresh_token.await?;
Generating an Authorization URL
use ;
let config = AuthorizeURLConfig ;
let url = authorize_url;
Parsing User Info from ID Token
use get_user_info_from_id_token;
let user = get_user_info_from_id_token?;
println!;
CloudKit Web Services
Setup
use AppleKeyPair;
use ;
let key_pair = from_file?;
let client = new?;
Record CRUD
use ;
// Create
let record = new
.with_name
.with_field
.with_field;
let created = client.create_record.await?;
// Update
let mut updated = created;
updated.fields.insert;
let updated = client.update_record.await?;
// Delete
client.delete_record.await?;
// Lookup
let records = client.lookup_records.await?;
Querying with QueryBuilder
use ;
let query = new
.filter
.sort
.build;
let response = client.query_records.await?;
for record in &response.records
Zone Management
use ;
let zone = client.create_zone.await?;
let zones = client.list_zones.await?;
client.delete_zone.await?;
Subscriptions & Push Notifications
use ;
let subscription = Subscription ;
let sub = client.create_subscription.await?;
let subs = client.list_subscriptions.await?;
client.delete_subscription.await?;
CloudKit Push Notification Parsing
Parse incoming APNs payloads containing CloudKit notification data:
use ;
let apns_json = r#"{
"aps": { "content-available": 1 },
"ck": {
"cid": "iCloud.com.company.app",
"nid": "notification-uuid",
"rid": { "recordName": "record-1", "zoneID": { "zoneName": "MyZone" } },
"rt": "MyRecordType",
"fo": 1,
"dbs": 2
}
}"#;
let notification = parse_notification?;
match notification
WebCourier Long-Polling
Poll for CloudKit notifications using Apple's webcourier service:
let notifications = client.poll_notifications
.await?;
for notification in notifications
Change Tracking
use ;
// Zone changes
let changes = client.fetch_zone_changes.await?;
// Use changes.sync_token for subsequent fetches
// changes.more_coming indicates if there are more changes
// Database changes
let db_changes = client.fetch_database_changes.await?;
User Discovery
let current_user = client.get_current_user.await?;
let users = client.discover_users.await?;
let found = client.lookup_users_by_email.await?;
Asset Uploads
use DatabaseType;
// Step 1: Request upload URL
let upload = client.request_asset_upload.await?;
// Step 2: Upload data to the returned URL
if let Some = upload.tokens.first
App Store Server API
Setup
use Arc;
use AppleKeyPair;
use ;
let key_pair = from_file?;
let client = new?;
Transaction History
use TransactionHistoryRequest;
// Get full transaction history
let history = client.get_transaction_history.await?;
for signed_tx in &history.signed_transactions
// Paginate with revision
if history.has_more
// With filters
let request = TransactionHistoryRequest ;
let filtered = client.get_transaction_history.await?;
Transaction Info
let info = client.get_transaction_info.await?;
println!;
Order Lookup
let order = client.look_up_order_id.await?;
println!; // Valid or Invalid
Subscription Status
let status = client.get_all_subscription_statuses.await?;
for group in &status.data
Extend Subscription Renewal Date
use ;
let request = ExtendRenewalDateRequest ;
let response = client.extend_renewal_date.await?;
println!;
Mass Extend Renewal Dates
use ;
let request = MassExtendRenewalDateRequest ;
let response = client.mass_extend_renewal_date.await?;
// Check status later
let status = client.get_mass_extension_status.await?;
println!;
Consumption Data
use *;
let request = ConsumptionRequest ;
client.send_consumption_data.await?;
Refund History
let refunds = client.get_refund_history.await?;
for signed_tx in &refunds.signed_transactions
// Paginate
if refunds.has_more
Test Notifications
// Request a test notification
let response = client.request_test_notification.await?;
// Check the status
let status = client.get_test_notification_status.await?;
println!;
Notification History
use ;
let request = NotificationHistoryRequest ;
let history = client.get_notification_history.await?;
for item in &history.notification_history
Retention Messaging
use ;
// Upload an image for retention messaging
let image_data = read.unwrap;
let image = client.upload_image.await?;
// List images
let images = client.get_image_list.await?;
// Upload a message
let message = UploadMessageRequest ;
let msg = client.upload_message.await?;
// List messages
let messages = client.get_message_list.await?;
// Configure a default message for a product
let default_msg = DefaultMessageRequest ;
client.configure_default_message.await?;
// Clean up
client.delete_message.await?;
client.delete_image.await?;
client.delete_default_message.await?;
JWS Signed Data Verification
Verify and decode signed transaction data, renewal info, and server notifications using X.509 certificate chain validation:
use ;
// Load Apple Root CA certificate
let root_cert = read.unwrap;
let verifier = new;
// Verify and decode a signed transaction
let transaction = verifier.verify_and_decode_transaction?;
println!;
println!;
println!;
// Verify and decode renewal info
let renewal = verifier.verify_and_decode_renewal_info?;
println!;
println!;
// Verify and decode a server notification
let notification = verifier.verify_and_decode_notification?;
println!;
println!;
// Verify and decode an app transaction
let app_tx = verifier.verify_and_decode_app_transaction?;
println!;
Handling Server Notifications V2
Parse webhook payloads from Apple's App Store Server Notifications V2:
use ;
// In your webhook handler, deserialize the request body
let body: ResponseBodyV2 = from_str?;
// Then verify and decode the signed payload
let decoded = verifier.verify_and_decode_notification?;
match decoded.notification_type
// Access the notification data
if let Some = &decoded.data
Handling Server Notifications V1 (Deprecated)
use ServerNotificationV1;
let notification: ServerNotificationV1 = from_str?;
println!;
if let Some = ¬ification.unified_receipt
Shared Key Management
You can share a single key pair between Apple Sign-In, CloudKit, and the App Store:
use AppleKeyPair;
use AppleAuthImpl;
use ;
use ;
let key_pair = from_file?;
// Use with Sign-In
let auth = from_key_pair?;
// Use with CloudKit
let cloudkit = new?;
// Use with App Store Server API
let appstore = new?;
Error Handling
All operations return Result<T, AppleError>. Each module has specific error variants:
use AppleError;
match result
App Store error codes can be inspected for programmatic handling:
use AppStoreErrorCode;
let code = from_code;
assert_eq!;
println!; // 4040010
License
MIT