Skip to content

Instantly share code, notes, and snippets.

@KunalKumarSwift
Created January 10, 2025 06:30
Show Gist options
  • Save KunalKumarSwift/2f8bc9f449de85ffbd50998ac5ad47a2 to your computer and use it in GitHub Desktop.
Save KunalKumarSwift/2f8bc9f449de85ffbd50998ac5ad47a2 to your computer and use it in GitHub Desktop.
import Foundation
class APIManager {
static let shared = APIManager()
private let baseURL = "http://localhost:3000" // Replace with your server's URL
/// Fetches a challenge from the server
func fetchChallenge(completion: @escaping (Result<(String, String), Error>) -> Void) {
guard let url = URL(string: "\(baseURL)/generate-challenge") else { return }
URLSession.shared.dataTask(with: url) { data, _, error in
if let error = error {
completion(.failure(error))
return
}
guard let data = data else {
completion(.failure(NSError(domain: "No data", code: -1, userInfo: nil)))
return
}
do {
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: String],
let challengeId = json["challengeId"],
let challenge = json["challenge"] {
completion(.success((challengeId, challenge)))
} else {
completion(.failure(NSError(domain: "Invalid response", code: -1, userInfo: nil)))
}
} catch {
completion(.failure(error))
}
}.resume()
}
/// Sends the attestation object to the server for verification
func verifyAttestation(challengeId: String, keyId: String, attestation: Data, completion: @escaping (Result<String, Error>) -> Void) {
guard let url = URL(string: "\(baseURL)/verify-attestation") else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body: [String: Any] = [
"challengeId": challengeId,
"keyId": keyId,
"attestation": attestation.base64EncodedString()
]
do {
request.httpBody = try JSONSerialization.data(withJSONObject: body, options: [])
} catch {
completion(.failure(error))
return
}
URLSession.shared.dataTask(with: request) { data, _, error in
if let error = error {
completion(.failure(error))
return
}
guard let data = data else {
completion(.failure(NSError(domain: "No data", code: -1, userInfo: nil)))
return
}
do {
if let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: String],
let message = json["message"] {
completion(.success(message))
} else {
completion(.failure(NSError(domain: "Invalid response", code: -1, userInfo: nil)))
}
} catch {
completion(.failure(error))
}
}.resume()
}
}
// view model:
import Foundation
import DeviceCheck
class ViewModel: ObservableObject {
@Published var statusMessage = ""
func performAppAttestation() {
// Step 1: Fetch Challenge from Server
APIManager.shared.fetchChallenge { [weak self] result in
switch result {
case .success(let (challengeId, challenge)):
self?.statusMessage = "Challenge fetched successfully!"
// Step 2: Generate Attestation using DCAppAttestService
guard DCAppAttestService.shared.isSupported else {
self?.statusMessage = "Device does not support App Attest."
return
}
DCAppAttestService.shared.generateKey { keyId, error in
if let error = error {
DispatchQueue.main.async {
self?.statusMessage = "Failed to generate key ID: \(error.localizedDescription)"
}
return
}
guard let keyId = keyId else { return }
DCAppAttestService.shared.attestKey(keyId, clientDataHash: Data(base64Encoded: challenge)!) { attestationData, error in
if let error = error {
DispatchQueue.main.async {
self?.statusMessage = "Failed to generate attestation object: \(error.localizedDescription)"
}
return
}
guard let attestationData = attestationData else { return }
// Step 3: Send Attestation to Server for Verification
APIManager.shared.verifyAttestation(challengeId: challengeId, keyId: keyId, attestation: attestationData) { result in
DispatchQueue.main.async {
switch result {
case .success(let message):
self?.statusMessage = message
case .failure(let error):
self?.statusMessage = "Verification failed: \(error.localizedDescription)"
}
}
}
}
}
case .failure(let error):
DispatchQueue.main.async {
self?.statusMessage = "Failed to fetch challenge: \(error.localizedDescription)"
}
}
}
}
}
// content view:
import SwiftUI
struct ContentView: View {
@StateObject private var viewModel = ViewModel()
var body: some View {
VStack(spacing: 20) {
Text("Apple App Attestation Demo")
.font(.headline)
Button(action: viewModel.performAppAttestation) {
Text("Start App Attestation")
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
Text(viewModel.statusMessage)
.padding()
.multilineTextAlignment(.center)
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment