{"id":18162814,"url":"https://github.com/zunda-pixel/appattest-swift","last_synced_at":"2026-07-04T14:00:38.142Z","repository":{"id":257812272,"uuid":"858723043","full_name":"zunda-pixel/appattest-swift","owner":"zunda-pixel","description":"App Attest Verifier for Server Side in Swift","archived":false,"fork":false,"pushed_at":"2026-06-28T01:07:48.000Z","size":109,"stargazers_count":7,"open_issues_count":1,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-06-28T03:06:46.778Z","etag":null,"topics":["apple","swift"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zunda-pixel.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-09-17T12:26:33.000Z","updated_at":"2026-06-28T01:07:46.000Z","dependencies_parsed_at":null,"dependency_job_id":"b0f7751f-6bbe-4ade-911c-f38aefa64412","html_url":"https://github.com/zunda-pixel/appattest-swift","commit_stats":null,"previous_names":["zunda-pixel/appattest-swift"],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/zunda-pixel/appattest-swift","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zunda-pixel%2Fappattest-swift","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zunda-pixel%2Fappattest-swift/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zunda-pixel%2Fappattest-swift/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zunda-pixel%2Fappattest-swift/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zunda-pixel","download_url":"https://codeload.github.com/zunda-pixel/appattest-swift/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zunda-pixel%2Fappattest-swift/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35124130,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-04T02:00:05.987Z","response_time":113,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apple","swift"],"created_at":"2024-11-02T10:05:20.378Z","updated_at":"2026-07-04T14:00:38.136Z","avatar_url":"https://github.com/zunda-pixel.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# AppAttest\n\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fzunda-pixel%2Fappattest-swift%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/zunda-pixel/appattest-swift)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fzunda-pixel%2Fappattest-swift%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/zunda-pixel/appattest-swift)\n\n\n## 1. [Server] Generate challenge and return to Client(iOS)\n\n```swift\nimport Foundation\nimport Crypto\n\n@main\nstruct App {\n  // DB or Server session data\n  var challenges: [Challenge] = []\n\n  mutating func generateChallenge(userId: UUID, sessionId: UUID) -\u003e Data {\n    let challenge = Challenge(\n      userId: userId,\n      sessionId: sessionId,\n      expiredAt: Date.now.addingTimeInterval(5 * 60), // expired after 5 minutes.\n      value: Data(AES.GCM.Nonce())\n    )\n\n    challenges.append(challenge)\n    return challenge.value\n  }\n}\n\nstruct Challenge {\n  var userId: UUID\n  var sessionId: UUID\n  var expiredAt: Date\n  var value: Data\n}\n```\n\n## 2. [Client(iOS)] Send Data to Server\n\n```swift\nimport Crypto\nimport DeviceCheck\nimport Foundation\n\nfunc sendData(\n  challenge: Data,\n  userId: UUID,\n  sessionId: UUID\n) async throws {\n  let service = DCAppAttestService.shared\n  let keyId = try await service.generateKey()\n\n  let attestation = try await service.attestKey(\n    keyId,\n    clientDataHash: Data(SHA256.hash(data: challenge))\n  )\n\n  let body = Body(\n    name: \"sample name\",\n    age: 25,\n    challenge: challenge,\n    keyId: keyId\n  )\n\n  let bodyData = try JSONEncoder().encode(body)\n  let assertion = try await service.generateAssertion(\n    keyId,\n    clientDataHash: Data(SHA256.hash(data: bodyData))\n  )\n\n  return (userId, sessionId, attestation, assertion, bodyData)\n}\n\nstruct Body: Codable {\n  let name: String\n  let age: Int\n  let challenge: Data\n  let keyId: String\n}\n```\n\n## 3. [Server] Verify data and Handle Body\n\n```swift\nimport AppAttest\nimport Foundation\n\n@main\nactor App {\n  var challenges: [Challenge] = []\n  var countersByKeyId: [String: UInt32] = [:]\n\n  func verifyAndHandleBody(\n    userId: UUID,\n    sessionId: UUID,\n    attestation: Data,\n    assertion: Data,\n    bodyData: Data\n  ) async throws {\n    let appIDPrefix = ProcessInfo.processInfo.environment[\"APP_ID_PREFIX\"]! // PH3HCZ4AK6\n    let bundleId = ProcessInfo.processInfo.environment[\"BUNDLE_ID\"]! // com.example.memo\n  \n    let appAttest = AppAttest(\n      appIDPrefix: appIDPrefix,\n      bundleId: bundleId,\n      environment: .development\n    )\n  \n    let body = try JSONDecoder().decode(Body.self, from: bodyData)\n\n    try verifyChallenge(\n      userId: userId,\n      sessionId: sessionId,\n      challengeData: body.challenge\n    )\n    \n    let attestation = try await appAttest.verifyAttestation(\n      challenge: body.challenge,\n      keyId: body.keyId,\n      attestation: attestation\n    )\n  \n    let previousCounter = countersByKeyId[body.keyId] ?? attestation.authenticatorData.counter\n    let counter = try appAttest.verifyAssertion(\n      assertion: assertion,\n      payload: bodyData,\n      certificate: attestation.statement.credentialCertificate,\n      counter: previousCounter\n    )\n    countersByKeyId[body.keyId] = counter\n    \n    print(body.name)\n    print(body.age)\n  }\n\n  func verifyChallenge(userId: UUID, sessionId: UUID, challengeData: Data) throws {\n    guard let challenge = challenges.first(where: { $0.userId == userId \u0026\u0026 $0.sessionId == sessionId \u0026\u0026 $0.value == challengeData }) else {\n      throw AppAttestError.challengeNotFound\n    }\n\n    guard Date.now \u003c= challenge.expiredAt else {\n      throw AppAttestError.challengeExpired\n    }\n\n    challenges.removeAll { $0.userId == userId \u0026\u0026 $0.sessionId == sessionId \u0026\u0026 $0.value == challengeData }\n  }\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzunda-pixel%2Fappattest-swift","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzunda-pixel%2Fappattest-swift","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzunda-pixel%2Fappattest-swift/lists"}