Authentication API
Quick Start
Advanced Features
Authentication
Learn how authentication works with Grantiva's API, including tenant identification and JWT token validation.
Overview
Grantiva uses a hybrid authentication model that automatically identifies your tenant based on your Apple Bundle ID and Team ID. No API keys are required - authentication is handled seamlessly through the attestation process.
Authentication Flow
How It Works
- Your iOS app initiates attestation with Apple's App Attest service
- The attestation includes your app's Bundle ID and Team ID
- Grantiva automatically identifies your tenant from these identifiers
- Upon successful validation, a JWT token is issued
- Use this JWT token for all subsequent API requests
No API Keys Required
Unlike traditional APIs, Grantiva doesn't require managing API keys. Your tenant is automatically identified through the secure App Attest process, eliminating the risk of exposed credentials.
// Traditional approach (NOT needed with Grantiva)
// client.apiKey = "sk_live_abc123..." ❌
// Grantiva approach - automatic authentication
let grantiva = Grantiva() // ✅ No configuration needed
let result = try await grantiva.validateAttestation()
JWT Token Structure
After successful attestation, you receive a JWT token with the following structure:
Token Header
{
"alg": "HS256",
"typ": "JWT"
}
Token Payload
{
"sub": "device_key_id_abc123", // Unique device identifier
"iss": "grantiva", // Token issuer
"aud": "com.yourcompany.app", // Your bundle ID
"iat": 1704063600, // Issued at timestamp
"exp": 1704067200, // Expiration timestamp
"tenantId": "tenant_xyz789", // Your tenant ID
"teamId": "ABCD1234", // Apple Team ID
"deviceIntelligence": {
"deviceId": "device_abc123",
"riskScore": 15,
"deviceIntegrity": "high",
"jailbreakDetected": false,
"attestationCount": 42,
"firstSeen": "2024-01-01T00:00:00Z",
"lastSeen": "2024-01-15T12:00:00Z"
},
"permissions": ["basic", "payments", "transfers"],
"customClaims": {
// Your custom claims (if configured)
}
}
Using the JWT Token
Include the JWT token in the Authorization header for all API requests:
HTTP Header Format
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Swift Example
var request = URLRequest(url: apiURL)
request.setValue("Bearer \(jwtToken)", forHTTPHeaderField: "Authorization")
let (data, response) = try await URLSession.shared.data(for: request)
cURL Example
curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \
https://api.grantiva.com/v1/device/status
Token Lifecycle
Token Expiration
- Default expiration: 1 hour from issuance
- Configurable per tenant (Professional tier and above)
- Check the
exp
claim for exact expiration time
Token Refresh
Tokens cannot be refreshed directly. When a token expires, perform a new attestation:
class TokenManager {
private var currentToken: String?
private var tokenExpiration: Date?
func getValidToken() async throws -> String {
// Check if current token is still valid
if let token = currentToken,
let expiration = tokenExpiration,
expiration > Date().addingTimeInterval(60) { // 1 minute buffer
return token
}
// Perform new attestation
let grantiva = Grantiva()
let result = try await grantiva.validateAttestation()
currentToken = result.token
tokenExpiration = result.expiresAt
return result.token
}
}
Security Considerations
Token Storage
Store tokens securely in the iOS Keychain, never in UserDefaults or plain text files
Token Transmission
Always use HTTPS when transmitting tokens. Never send tokens via URL parameters
Token Validation
Always validate tokens server-side. Check expiration, signature, and claims
Token Scope
Tokens are scoped to specific devices and cannot be transferred between devices
Error Responses
Authentication errors return standard HTTP status codes:
// 401 Unauthorized - No token provided
{
"error": "unauthorized",
"message": "No authentication token provided"
}
// 403 Forbidden - Invalid or expired token
{
"error": "forbidden",
"message": "Token expired or invalid"
}
// 403 Forbidden - Device risk too high
{
"error": "forbidden",
"message": "Device risk score exceeds threshold",
"riskScore": 85
}