From 9d3c7139a29c073196db0a2eaca631c2702074fd Mon Sep 17 00:00:00 2001 From: Morris Richman <81453549+Mcrich23@users.noreply.github.com> Date: Sun, 27 Jul 2025 21:59:55 -0700 Subject: [PATCH 01/15] Update Package.swift --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index c53159f..16fe483 100644 --- a/Package.swift +++ b/Package.swift @@ -14,7 +14,7 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/vapor/vapor.git", from: "4.14.0"), - .package(url: "https://github.com/vapor/jwt.git", from: "4.0.0-rc.2.1") + .package(url: "https://github.com/vapor/jwt.git", from: "5.0.0") ], targets: [ .target( From 964297aa195949de746bcb88bca86acc80ed44da Mon Sep 17 00:00:00 2001 From: Morris Richman <81453549+Mcrich23@users.noreply.github.com> Date: Sun, 27 Jul 2025 22:02:29 -0700 Subject: [PATCH 02/15] Update Package.swift --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 16fe483..730d313 100644 --- a/Package.swift +++ b/Package.swift @@ -5,7 +5,7 @@ import PackageDescription let package = Package( name: "VaporDeviceCheck", platforms: [ - .macOS(.v10_15) + .macOS(.v13) ], products: [ .library( From 79f096b6e027939f0d70cdd9ed3dc039aeb2d3a8 Mon Sep 17 00:00:00 2001 From: Morris Richman <81453549+Mcrich23@users.noreply.github.com> Date: Sun, 27 Jul 2025 22:07:53 -0700 Subject: [PATCH 03/15] package fixes --- Package.resolved | 422 ++++++++++++++++++++++++++++++----------------- Package.swift | 4 +- 2 files changed, 275 insertions(+), 151 deletions(-) diff --git a/Package.resolved b/Package.resolved index 5ce1f3f..e2d0cd7 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,151 +1,275 @@ { - "object": { - "pins": [ - { - "package": "async-http-client", - "repositoryURL": "https://github.com/swift-server/async-http-client.git", - "state": { - "branch": null, - "revision": "037b70291941fe43de668066eb6fb802c5e181d2", - "version": "1.1.1" - } - }, - { - "package": "async-kit", - "repositoryURL": "https://github.com/vapor/async-kit.git", - "state": { - "branch": null, - "revision": "7457413e57dbfac762b32dd30c1caf2c55a02a3d", - "version": "1.2.0" - } - }, - { - "package": "console-kit", - "repositoryURL": "https://github.com/vapor/console-kit.git", - "state": { - "branch": null, - "revision": "9b5842b47be1a3164a42811613dce09bf5bf1f91", - "version": "4.1.3" - } - }, - { - "package": "jwt", - "repositoryURL": "https://github.com/vapor/jwt.git", - "state": { - "branch": null, - "revision": "245dc755c2202a21c4108de0ecfd05aab174e8ab", - "version": "4.0.0-rc.2.1" - } - }, - { - "package": "jwt-kit", - "repositoryURL": "https://github.com/vapor/jwt-kit.git", - "state": { - "branch": null, - "revision": "b5b711fd6ee5638552a29a0c39003aeb109caa94", - "version": "4.0.0-rc.1.5" - } - }, - { - "package": "routing-kit", - "repositoryURL": "https://github.com/vapor/routing-kit.git", - "state": { - "branch": null, - "revision": "e7f2d5bd36dc65a9edb303541cb678515a7fece3", - "version": "4.1.0" - } - }, - { - "package": "swift-backtrace", - "repositoryURL": "https://github.com/swift-server/swift-backtrace.git", - "state": { - "branch": null, - "revision": "f2fd8c4845a123419c348e0bc4b3839c414077d5", - "version": "1.2.0" - } - }, - { - "package": "swift-crypto", - "repositoryURL": "https://github.com/apple/swift-crypto.git", - "state": { - "branch": null, - "revision": "9b9d1868601a199334da5d14f4ab2d37d4f8d0c5", - "version": "1.0.2" - } - }, - { - "package": "swift-log", - "repositoryURL": "https://github.com/apple/swift-log.git", - "state": { - "branch": null, - "revision": "57c6bd04256ba47590ee2285e208f731210c5c10", - "version": "1.3.0" - } - }, - { - "package": "swift-metrics", - "repositoryURL": "https://github.com/apple/swift-metrics.git", - "state": { - "branch": null, - "revision": "708b960b4605abb20bc55d65abf6bad607252200", - "version": "2.0.0" - } - }, - { - "package": "swift-nio", - "repositoryURL": "https://github.com/apple/swift-nio.git", - "state": { - "branch": null, - "revision": "8a865bd15e69526cbdfcfd7c47698eb20b2ba951", - "version": "2.19.0" - } - }, - { - "package": "swift-nio-extras", - "repositoryURL": "https://github.com/apple/swift-nio-extras.git", - "state": { - "branch": null, - "revision": "7cd24c0efcf9700033f671b6a8eaa64a77dd0b72", - "version": "1.5.1" - } - }, - { - "package": "swift-nio-http2", - "repositoryURL": "https://github.com/apple/swift-nio-http2.git", - "state": { - "branch": null, - "revision": "c76a9a5085bfc22882f8cff88189662af30806e8", - "version": "1.12.3" - } - }, - { - "package": "swift-nio-ssl", - "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", - "state": { - "branch": null, - "revision": "d381bc53edd9de88a75480a2b969bfc26d61ee76", - "version": "2.8.0" - } - }, - { - "package": "vapor", - "repositoryURL": "https://github.com/vapor/vapor.git", - "state": { - "branch": null, - "revision": "dc2aa1e02e04a47b67cb0dabed628fe844900f30", - "version": "4.14.0" - } - }, - { - "package": "websocket-kit", - "repositoryURL": "https://github.com/vapor/websocket-kit.git", - "state": { - "branch": null, - "revision": "021edd1ca55451ad15b3e84da6b4064e4b877b34", - "version": "2.1.0" - } - } - ] - }, - "version": 1 + "pins" : [ + { + "identity" : "async-http-client", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swift-server/async-http-client.git", + "state" : { + "revision" : "60235983163d040f343a489f7e2e77c1918a8bd9", + "version" : "1.26.1" + } + }, + { + "identity" : "async-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/async-kit.git", + "state" : { + "revision" : "e048c8ee94967e8d8a1c2ec0e1156d6f7fa34d31", + "version" : "1.20.0" + } + }, + { + "identity" : "console-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/console-kit.git", + "state" : { + "revision" : "742f624a998cba2a9e653d9b1e91ad3f3a5dff6b", + "version" : "4.15.2" + } + }, + { + "identity" : "jwt", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/jwt.git", + "state" : { + "revision" : "af1c59762d70d1065ddbc0d7902ea9b3dacd1a26", + "version" : "5.1.2" + } + }, + { + "identity" : "jwt-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/jwt-kit.git", + "state" : { + "revision" : "2033b3e661238dda3d30e36a2d40987499d987de", + "version" : "5.2.0" + } + }, + { + "identity" : "multipart-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/multipart-kit.git", + "state" : { + "revision" : "3498e60218e6003894ff95192d756e238c01f44e", + "version" : "4.7.1" + } + }, + { + "identity" : "routing-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/routing-kit.git", + "state" : { + "revision" : "93f7222c8e195cbad39fafb5a0e4cc85a8def7ea", + "version" : "4.9.2" + } + }, + { + "identity" : "swift-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-algorithms.git", + "state" : { + "revision" : "87e50f483c54e6efd60e885f7f5aa946cee68023", + "version" : "1.2.1" + } + }, + { + "identity" : "swift-asn1", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-asn1.git", + "state" : { + "revision" : "f70225981241859eb4aa1a18a75531d26637c8cc", + "version" : "1.4.0" + } + }, + { + "identity" : "swift-async-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-async-algorithms.git", + "state" : { + "revision" : "042e1c4d9d19748c9c228f8d4ebc97bb1e339b0b", + "version" : "1.0.4" + } + }, + { + "identity" : "swift-atomics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-atomics.git", + "state" : { + "revision" : "b601256eab081c0f92f059e12818ac1d4f178ff7", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-certificates", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-certificates.git", + "state" : { + "revision" : "870f4d5fe5fcfedc13f25d70e103150511746404", + "version" : "1.11.0" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "8c0c0a8b49e080e54e5e328cc552821ff07cd341", + "version" : "1.2.1" + } + }, + { + "identity" : "swift-crypto", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-crypto.git", + "state" : { + "revision" : "84b1d494118d63629a785230135f82991f02329e", + "version" : "3.13.2" + } + }, + { + "identity" : "swift-distributed-tracing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-distributed-tracing.git", + "state" : { + "revision" : "b78796709d243d5438b36e74ce3c5ec2d2ece4d8", + "version" : "1.2.1" + } + }, + { + "identity" : "swift-http-structured-headers", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-http-structured-headers.git", + "state" : { + "revision" : "db6eea3692638a65e2124990155cd220c2915903", + "version" : "1.3.0" + } + }, + { + "identity" : "swift-http-types", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-http-types.git", + "state" : { + "revision" : "a0a57e949a8903563aba4615869310c0ebf14c03", + "version" : "1.4.0" + } + }, + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log.git", + "state" : { + "revision" : "ce592ae52f982c847a4efc0dd881cc9eb32d29f2", + "version" : "1.6.4" + } + }, + { + "identity" : "swift-metrics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-metrics.git", + "state" : { + "revision" : "4c83e1cdf4ba538ef6e43a9bbd0bcc33a0ca46e3", + "version" : "2.7.0" + } + }, + { + "identity" : "swift-nio", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio.git", + "state" : { + "revision" : "a5fea865badcb1c993c85b0f0e8d05a4bd2270fb", + "version" : "2.85.0" + } + }, + { + "identity" : "swift-nio-extras", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-extras.git", + "state" : { + "revision" : "a55c3dd3a81d035af8a20ce5718889c0dcab073d", + "version" : "1.29.0" + } + }, + { + "identity" : "swift-nio-http2", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-http2.git", + "state" : { + "revision" : "5e9e99ec96c53bc2c18ddd10c1e25a3cd97c55e5", + "version" : "1.38.0" + } + }, + { + "identity" : "swift-nio-ssl", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-ssl.git", + "state" : { + "revision" : "385f5bd783ffbfff46b246a7db7be8e4f04c53bd", + "version" : "2.33.0" + } + }, + { + "identity" : "swift-nio-transport-services", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-nio-transport-services.git", + "state" : { + "revision" : "decfd235996bc163b44e10b8a24997a3d2104b90", + "version" : "1.25.0" + } + }, + { + "identity" : "swift-numerics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-numerics.git", + "state" : { + "revision" : "e0ec0f5f3af6f3e4d5e7a19d2af26b481acb6ba8", + "version" : "1.0.3" + } + }, + { + "identity" : "swift-service-context", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-service-context.git", + "state" : { + "revision" : "1983448fefc717a2bc2ebde5490fe99873c5b8a6", + "version" : "1.2.1" + } + }, + { + "identity" : "swift-service-lifecycle", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swift-server/swift-service-lifecycle.git", + "state" : { + "revision" : "e7187309187695115033536e8fc9b2eb87fd956d", + "version" : "2.8.0" + } + }, + { + "identity" : "swift-system", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-system.git", + "state" : { + "revision" : "41daa93a5d229e1548ec86ab527ce4783ca84dda", + "version" : "1.6.0" + } + }, + { + "identity" : "vapor", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/vapor.git", + "state" : { + "revision" : "3636f443474769147828a5863e81a31f6f30e92c", + "version" : "4.115.1" + } + }, + { + "identity" : "websocket-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/websocket-kit.git", + "state" : { + "revision" : "8666c92dbbb3c8eefc8008c9c8dcf50bfd302167", + "version" : "2.16.1" + } + } + ], + "version" : 2 } diff --git a/Package.swift b/Package.swift index 730d313..19d6601 100644 --- a/Package.swift +++ b/Package.swift @@ -1,11 +1,11 @@ -// swift-tools-version:5.2 +// swift-tools-version:5.8 import PackageDescription let package = Package( name: "VaporDeviceCheck", platforms: [ - .macOS(.v13) + .macOS(.v13) ], products: [ .library( From 974c0f1fef70d382070a38350dff579a745da405 Mon Sep 17 00:00:00 2001 From: Morris Richman <81453549+Mcrich23@users.noreply.github.com> Date: Sun, 27 Jul 2025 22:34:37 -0700 Subject: [PATCH 04/15] jwt dependency updates --- .../AppleDeviceCheckClient.swift | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/Sources/VaporDeviceCheck/AppleDeviceCheckClient.swift b/Sources/VaporDeviceCheck/AppleDeviceCheckClient.swift index 2d3f76e..82375ac 100644 --- a/Sources/VaporDeviceCheck/AppleDeviceCheckClient.swift +++ b/Sources/VaporDeviceCheck/AppleDeviceCheckClient.swift @@ -1,19 +1,26 @@ import Vapor import JWT -public struct AppleDeviceCheckClient: DeviceCheckClient { +public struct AppleDeviceCheckClient: DeviceCheckClient, Sendable { public let jwkKid: JWKIdentifier public let jwkIss: String public func request(_ request: Request, deviceToken: String, isSandbox: Bool) -> EventLoopFuture { - request.client.post(URI(string: "https://\(isSandbox ? "api.development" : "api").devicecheck.apple.com/v1/validate_device_token")) { - $0.headers.add(name: .authorization, value: "Bearer \(try signedJwt(for: request))") - return try $0.content.encode(DeviceCheckRequest(deviceToken: deviceToken)) + let promise = request.eventLoop.makePromise(of: ClientResponse.self) + + promise.completeWithTask { + var response = try await request.client.post(URI(string: "https://\(isSandbox ? "api.development" : "api").devicecheck.apple.com/v1/validate_device_token")) + response.headers.add(name: .authorization, value: "Bearer \(try await signedJwt(for: request))") + try response.content.encode(DeviceCheckRequest(deviceToken: deviceToken)) + + return response } + + return promise.futureResult } - private func signedJwt(for request: Request) throws -> String { - try request.jwt.sign(DeviceCheckJWT(iss: jwkIss), kid: jwkKid) + private func signedJwt(for request: Request) async throws -> String { + try await request.jwt.sign(DeviceCheckJWT(iss: jwkIss), kid: jwkKid) } } @@ -21,7 +28,7 @@ private struct DeviceCheckJWT: JWTPayload { let iss: String let iat: Int = Int(Date().timeIntervalSince1970) - func verify(using signer: JWTSigner) throws { + func verify(using algorithm: some JWTKit.JWTAlgorithm) async throws { //no-op } } From 84f4dcec1ef092b8661206282f273c9956b6a70c Mon Sep 17 00:00:00 2001 From: Morris Richman <81453549+Mcrich23@users.noreply.github.com> Date: Sun, 27 Jul 2025 23:12:35 -0700 Subject: [PATCH 05/15] Add bypass tokens --- Sources/VaporDeviceCheck/DeviceCheck.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Sources/VaporDeviceCheck/DeviceCheck.swift b/Sources/VaporDeviceCheck/DeviceCheck.swift index d3de632..412e875 100644 --- a/Sources/VaporDeviceCheck/DeviceCheck.swift +++ b/Sources/VaporDeviceCheck/DeviceCheck.swift @@ -8,10 +8,13 @@ public struct NoAppleDeviceTokenError: DebuggableError { public struct DeviceCheck: Middleware { let excludes: [[PathComponent]]? + /// Tokens to include via environment to bypass device check. Designed for applications like the xcode simulator + let bypassTokens: Set let client: DeviceCheckClient - public init(jwkKid: JWKIdentifier, jwkIss: String, excludes: [[PathComponent]]? = nil, client: DeviceCheckClient? = nil) { + public init(jwkKid: JWKIdentifier, jwkIss: String, excludes: [[PathComponent]]? = nil, bypassTokens: Set = [], client: DeviceCheckClient? = nil) { self.excludes = excludes + self.bypassTokens = bypassTokens self.client = client ?? AppleDeviceCheckClient(jwkKid: jwkKid, jwkIss: jwkIss) } @@ -23,6 +26,9 @@ public struct DeviceCheck: Middleware { if excludes?.map({ $0.string }).contains(where: { $0 == request.route?.path.string }) ?? false { return next.respond(to: request) } + if bypassTokens.contains(where: { $0 == request.headers.first(name: .xAppleDeviceToken) }) { + return next.respond(to: request) + } guard let xAppleDeviceToken = request.headers.first(name: .xAppleDeviceToken) else { return request.eventLoop.makeFailedFuture(NoAppleDeviceTokenError()) From 5c0d36ed81bc72b79da3c413db417ba24661fd31 Mon Sep 17 00:00:00 2001 From: Morris Richman <81453549+Mcrich23@users.noreply.github.com> Date: Mon, 28 Jul 2025 00:50:00 -0700 Subject: [PATCH 06/15] added real environment detection --- Sources/VaporDeviceCheck/DeviceCheck.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/VaporDeviceCheck/DeviceCheck.swift b/Sources/VaporDeviceCheck/DeviceCheck.swift index 412e875..2bbca09 100644 --- a/Sources/VaporDeviceCheck/DeviceCheck.swift +++ b/Sources/VaporDeviceCheck/DeviceCheck.swift @@ -19,7 +19,7 @@ public struct DeviceCheck: Middleware { } public func respond(to request: Request, chainingTo next: Responder) -> EventLoopFuture { - requestDeviceCheck(on: request, chainingTo: next, isSandbox: false) + requestDeviceCheck(on: request, chainingTo: next, isSandbox: [Environment.development, Environment.testing].contains((try? Environment.detect()) ?? .production)) } private func requestDeviceCheck(on request: Request, chainingTo next: Responder, isSandbox: Bool) -> EventLoopFuture { From 6f705e543b2e8be718cada998311fe859f6f4f71 Mon Sep 17 00:00:00 2001 From: Morris Richman <81453549+Mcrich23@users.noreply.github.com> Date: Mon, 28 Jul 2025 01:15:49 -0700 Subject: [PATCH 07/15] logic request fixes --- .../xcschemes/VaporDeviceCheck.xcscheme | 79 +++++++++++++++++++ .../AppleDeviceCheckClient.swift | 7 +- 2 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/VaporDeviceCheck.xcscheme diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/VaporDeviceCheck.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/VaporDeviceCheck.xcscheme new file mode 100644 index 0000000..c26c341 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/VaporDeviceCheck.xcscheme @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Sources/VaporDeviceCheck/AppleDeviceCheckClient.swift b/Sources/VaporDeviceCheck/AppleDeviceCheckClient.swift index 82375ac..5dc4257 100644 --- a/Sources/VaporDeviceCheck/AppleDeviceCheckClient.swift +++ b/Sources/VaporDeviceCheck/AppleDeviceCheckClient.swift @@ -9,9 +9,10 @@ public struct AppleDeviceCheckClient: DeviceCheckClient, Sendable { let promise = request.eventLoop.makePromise(of: ClientResponse.self) promise.completeWithTask { - var response = try await request.client.post(URI(string: "https://\(isSandbox ? "api.development" : "api").devicecheck.apple.com/v1/validate_device_token")) - response.headers.add(name: .authorization, value: "Bearer \(try await signedJwt(for: request))") - try response.content.encode(DeviceCheckRequest(deviceToken: deviceToken)) + var headers = HTTPHeaders() + headers.add(name: .authorization, value: "Bearer \(try await signedJwt(for: request))") + + let response = try await request.client.put((URI(string: "https://\(isSandbox ? "api.development" : "api").devicecheck.apple.com/v1/validate_device_token")), headers: headers, content: DeviceCheckRequest(deviceToken: deviceToken)) return response } From 38c40cd7955781f664e5d9821b79deb4fd8ce333 Mon Sep 17 00:00:00 2001 From: Morris Richman <81453549+Mcrich23@users.noreply.github.com> Date: Mon, 28 Jul 2025 01:19:15 -0700 Subject: [PATCH 08/15] Update DeviceCheckRequest.swift --- Sources/VaporDeviceCheck/DeviceCheckRequest.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/VaporDeviceCheck/DeviceCheckRequest.swift b/Sources/VaporDeviceCheck/DeviceCheckRequest.swift index ad39b97..9d33f56 100644 --- a/Sources/VaporDeviceCheck/DeviceCheckRequest.swift +++ b/Sources/VaporDeviceCheck/DeviceCheckRequest.swift @@ -3,7 +3,7 @@ import Vapor struct DeviceCheckRequest: Content { let deviceToken: String let transactionId: String = UUID().uuidString - let timestamp: Int = Int(Date().timeIntervalSince1970 * 1000) + let timestamp: Int = Int(Date().timeIntervalSince1970) enum CodingKeys: String, CodingKey { case deviceToken = "device_token" From b5c51367e0a50d8d1cac6f8f3609833401e33ef3 Mon Sep 17 00:00:00 2001 From: Morris Richman <81453549+Mcrich23@users.noreply.github.com> Date: Mon, 28 Jul 2025 09:47:05 -0700 Subject: [PATCH 09/15] Revert "Update DeviceCheckRequest.swift" This reverts commit 38c40cd7955781f664e5d9821b79deb4fd8ce333. --- Sources/VaporDeviceCheck/DeviceCheckRequest.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/VaporDeviceCheck/DeviceCheckRequest.swift b/Sources/VaporDeviceCheck/DeviceCheckRequest.swift index 9d33f56..ad39b97 100644 --- a/Sources/VaporDeviceCheck/DeviceCheckRequest.swift +++ b/Sources/VaporDeviceCheck/DeviceCheckRequest.swift @@ -3,7 +3,7 @@ import Vapor struct DeviceCheckRequest: Content { let deviceToken: String let transactionId: String = UUID().uuidString - let timestamp: Int = Int(Date().timeIntervalSince1970) + let timestamp: Int = Int(Date().timeIntervalSince1970 * 1000) enum CodingKeys: String, CodingKey { case deviceToken = "device_token" From 1ec7341ec76921bdd5156858f05008bfd67aacae Mon Sep 17 00:00:00 2001 From: Morris Richman <81453549+Mcrich23@users.noreply.github.com> Date: Mon, 28 Jul 2025 09:52:05 -0700 Subject: [PATCH 10/15] Update AppleDeviceCheckClient.swift --- Sources/VaporDeviceCheck/AppleDeviceCheckClient.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/VaporDeviceCheck/AppleDeviceCheckClient.swift b/Sources/VaporDeviceCheck/AppleDeviceCheckClient.swift index 5dc4257..859eb5e 100644 --- a/Sources/VaporDeviceCheck/AppleDeviceCheckClient.swift +++ b/Sources/VaporDeviceCheck/AppleDeviceCheckClient.swift @@ -12,7 +12,7 @@ public struct AppleDeviceCheckClient: DeviceCheckClient, Sendable { var headers = HTTPHeaders() headers.add(name: .authorization, value: "Bearer \(try await signedJwt(for: request))") - let response = try await request.client.put((URI(string: "https://\(isSandbox ? "api.development" : "api").devicecheck.apple.com/v1/validate_device_token")), headers: headers, content: DeviceCheckRequest(deviceToken: deviceToken)) + let response = try await request.client.post((URI(string: "https://\(isSandbox ? "api.development" : "api").devicecheck.apple.com/v1/validate_device_token")), headers: headers, content: DeviceCheckRequest(deviceToken: deviceToken)) return response } From 1ff68f278cc5dcf9134c4151ced8b31cfd4c7000 Mon Sep 17 00:00:00 2001 From: Morris Richman <81453549+Mcrich23@users.noreply.github.com> Date: Mon, 28 Jul 2025 09:57:30 -0700 Subject: [PATCH 11/15] Update README.md --- README.md | 54 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index a93ccf1..8cffe90 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,11 @@ First add the package to your `Package.swift`: .package(url: "https://github.com/Bearologics/VaporDeviceCheck", from: "1.0.1") ``` -Then configure your Vapor `Application` and make sure to set up the JWT credentials to authenticate against the DeviceCheck API, in this example we're using environment variables which are prefixed `APPLE_JWT_` and install the Middleware: +Then configure your Vapor `Application` and make sure to set up the JWT credentials to authenticate against the DeviceCheck API, in this example we're using environment variables which are prefixed `APPLE_JWT_` and install the Middleware with this setup function: ```swift +import Vapor +import JWTKit import VaporDeviceCheck enum ConfigurationError: Error { @@ -20,37 +22,49 @@ enum ConfigurationError: Error { } // configures your application -public func configure(_ app: Application) throws { - guard let jwtPrivateKeyString = Environment.get("APPLE_JWT_PRIVATE_KEY") else { +public func configureDeviceCheck(_ app: Application) async throws { + guard let jwtPrivateKeyStringEscaped = Environment.get("APPLE_JWT_PRIVATE_KEY") else { throw ConfigurationError.noAppleJwtPrivateKey } - + let jwtPrivateKeyString = jwtPrivateKeyStringEscaped.replacingOccurrences(of: "\\n", with: "\n") + guard let jwtKidString = Environment.get("APPLE_JWT_KID") else { throw ConfigurationError.noAppleJwtKid } - - guard let jwkIssString = Environment.get("APPLE_JWT_ISS") else { + guard let jwtIss = Environment.get("APPLE_JWT_ISS") else { throw ConfigurationError.noAppleJwtIss } - - let jwkKid = JWKIdentifier(string: jwtKidString) - - app.jwt.signers.use( - .es256(key: try! .private(pem: jwtPrivateKeyString.data(using: .utf8)!)), - kid: jwkKid, - isDefault: false - ) - - // install middleware - app.middleware.use(DeviceCheck(jwkKid: jwkKid, jwkIss: jwkIssString, excludes: [["health"]])) - - // register routes - try routes(app) + + let jwtBypassToken = Environment.get("APPLE_JWT_BYPASS_TOKEN") + + let kid = JWKIdentifier(string: jwtKidString) + let privateKey = try ES256PrivateKey(pem: Data(jwtPrivateKeyString.utf8)) + + // Add ECDSA key with JWKIdentifier + await app.jwt.keys.add(ecdsa: privateKey, kid: kid) + + app.middleware.use(DeviceCheck( + jwkKid: kid, + jwkIss: jwtIss, + excludes: [["health"]], + bypassTokens: jwtBypassToken == nil ? [] : [jwtBypassToken!] + )) +} +``` + +Then you call it from configure: + +```swift +public func configure(_ app: Application) async throws { + try await configureDeviceCheck(app) + ... } ``` That's basically it, from now on, every request that'll pass the Middleware will require a valid `X-Apple-Device-Token` header to be set, otherwise it will be rejected. +> **Note:** You can pass in the private key either multilined or single-lined separated by `\n` and it will parse the key correctly. + ## 🔑 Setting up your App / Retrieving a DeviceCheck Token You'll need to import Apple's `DeviceCheck` Framework to retrieve a token for your device. From d13707019af87c049512943a06f24b9d296e2172 Mon Sep 17 00:00:00 2001 From: Morris Richman <81453549+Mcrich23@users.noreply.github.com> Date: Mon, 28 Jul 2025 10:01:59 -0700 Subject: [PATCH 12/15] Update README.md --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8cffe90..c0ae810 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,15 @@ public func configure(_ app: Application) async throws { That's basically it, from now on, every request that'll pass the Middleware will require a valid `X-Apple-Device-Token` header to be set, otherwise it will be rejected. -> **Note:** You can pass in the private key either multilined or single-lined separated by `\n` and it will parse the key correctly. +> **Note:** You can pass in the private key either multilined or single-lined separated by `\n` and it will parse the key correctly. + +## Environment Variable Breakdown +| Environment Key | Description | +| ---------------------- | ----------------------------------------------------------------------------------------------------------- | +| APPLE_JWT_PRIVATE_KEY | The full private key you downloaded as a string. | +| APPLE_JWT_KID | The id of your private key. This is found in the management panel for your private key. | +| APPLE_JWT_ISS | Your Apple Developer team id. | +| APPLE_JWT_BYPASS_TOKEN | A token to use to bypass device check. This is helpful for development with the Xcode Simulator. (Optional) | ## 🔑 Setting up your App / Retrieving a DeviceCheck Token From 283ee695077837557e930228e6fcca69671c39ba Mon Sep 17 00:00:00 2001 From: Morris Richman <81453549+Mcrich23@users.noreply.github.com> Date: Mon, 28 Jul 2025 10:08:04 -0700 Subject: [PATCH 13/15] make bypass token do base64 decoding when checking for bypass token --- Sources/VaporDeviceCheck/DeviceCheck.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Sources/VaporDeviceCheck/DeviceCheck.swift b/Sources/VaporDeviceCheck/DeviceCheck.swift index 2bbca09..6000cfd 100644 --- a/Sources/VaporDeviceCheck/DeviceCheck.swift +++ b/Sources/VaporDeviceCheck/DeviceCheck.swift @@ -26,13 +26,14 @@ public struct DeviceCheck: Middleware { if excludes?.map({ $0.string }).contains(where: { $0 == request.route?.path.string }) ?? false { return next.respond(to: request) } - if bypassTokens.contains(where: { $0 == request.headers.first(name: .xAppleDeviceToken) }) { - return next.respond(to: request) - } guard let xAppleDeviceToken = request.headers.first(name: .xAppleDeviceToken) else { return request.eventLoop.makeFailedFuture(NoAppleDeviceTokenError()) } + + if bypassTokens.contains(where: { $0 == (String(data: Data(base64Encoded: xAppleDeviceToken) ?? Data(), encoding: .utf8) ?? xAppleDeviceToken) }) { + return next.respond(to: request) + } return client.request(request, deviceToken: xAppleDeviceToken, isSandbox: isSandbox) .flatMap { res in From 61028117d680eb029bffdefe033c8db9b58c3bd0 Mon Sep 17 00:00:00 2001 From: Morris Richman <81453549+Mcrich23@users.noreply.github.com> Date: Sat, 4 Oct 2025 16:51:20 -0700 Subject: [PATCH 14/15] Update DeviceCheck.swift --- Sources/VaporDeviceCheck/DeviceCheck.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/VaporDeviceCheck/DeviceCheck.swift b/Sources/VaporDeviceCheck/DeviceCheck.swift index 6000cfd..f2bb78b 100644 --- a/Sources/VaporDeviceCheck/DeviceCheck.swift +++ b/Sources/VaporDeviceCheck/DeviceCheck.swift @@ -41,9 +41,9 @@ public struct DeviceCheck: Middleware { return next.respond(to: request) } - if isSandbox { - return request.eventLoop.makeFailedFuture(Abort(.unauthorized)) - } +// if isSandbox { +// return request.eventLoop.makeFailedFuture(Abort(.unauthorized)) +// } return self.requestDeviceCheck(on: request, chainingTo: next, isSandbox: true) } From 3e158c23015856d3e2925875202634cc5e6965e7 Mon Sep 17 00:00:00 2001 From: Morris Richman <81453549+Mcrich23@users.noreply.github.com> Date: Sat, 4 Oct 2025 16:52:35 -0700 Subject: [PATCH 15/15] Revert "Update DeviceCheck.swift" This reverts commit 61028117d680eb029bffdefe033c8db9b58c3bd0. --- Sources/VaporDeviceCheck/DeviceCheck.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/VaporDeviceCheck/DeviceCheck.swift b/Sources/VaporDeviceCheck/DeviceCheck.swift index f2bb78b..6000cfd 100644 --- a/Sources/VaporDeviceCheck/DeviceCheck.swift +++ b/Sources/VaporDeviceCheck/DeviceCheck.swift @@ -41,9 +41,9 @@ public struct DeviceCheck: Middleware { return next.respond(to: request) } -// if isSandbox { -// return request.eventLoop.makeFailedFuture(Abort(.unauthorized)) -// } + if isSandbox { + return request.eventLoop.makeFailedFuture(Abort(.unauthorized)) + } return self.requestDeviceCheck(on: request, chainingTo: next, isSandbox: true) }