Skip to content

Instantly share code, notes, and snippets.

@kdeda
Last active October 22, 2024 04:40
Show Gist options
  • Save kdeda/b49ba9f2b9b1a400a17b5fae2039cbbc to your computer and use it in GitHub Desktop.
Save kdeda/b49ba9f2b9b1a400a17b5fae2039cbbc to your computer and use it in GitHub Desktop.
Verifying a Sign in with Apple JWT in Vapor 4
// The model
public struct UserCredentials: Equatable, Codable {
// This silly variable can come nback as emoty, but no fret it is encrypted inside the jwtToken
// and the server will be able to extract it.
public var email: String
public var jwtToken: String
public var userId: String
public var isEmpty: Bool {
email.isEmpty
&& jwtToken.isEmpty
&& userId.isEmpty
}
public init() {
self.email = ""
self.jwtToken = ""
self.userId = ""
}
public init(email: String = "", tokenData: Data = Data(), userId: String = "") {
self.email = email
self.jwtToken = String.init(data: tokenData, encoding: .utf8) ?? ""
self.userId = userId
}
}
// The client
fileprivate extension SignInWithAppleButton {
init(onCompletion: @escaping (UserCredentials?) -> Void) {
self.init(.continue) { request in
request.requestedScopes = [.email]
} onCompletion: { result in
guard let auth = try? result.get(),
let credential = auth.credential as? ASAuthorizationAppleIDCredential
else {
onCompletion(UserCredentials())
return
}
onCompletion(UserCredentials(
email: credential.email ?? "",
tokenData: credential.identityToken ?? Data(),
userId: credential.user
))
}
}
}
// Vapor server
fileprivate extension Request {
struct UserCredentials {
public var email: String
public var bundleID: String
public var expiration: Date
}
/**
https://github.com/vapor/jwt-kit
https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_rest_api
https://github.com/vapor/jwt-kit/blob/02a0fa600eee1bdc892013d62fc795fc623a5cc3/Sources/JWTKit/Vendor/AppleIdentityToken.swift#L3
https://swiftinit.org/docs/vapor.jwt-kit/jwtkit/appleidentitytoken
*/
func validateJWT() async throws -> UserCredentials {
guard let jwtToken = headers[URLRequest.appleJWTToken].first
else {
Log4swift[Self.self].error("bad request: '\(self)'")
throw ServerError.badRequest
}
do {
let clientResponse = try await client.get("https://appleid.apple.com/auth/keys")
let jwks = try clientResponse.content.decode(JWKS.self)
let keys = try await JWTKeyCollection().add(jwks: jwks)
let payload = try await keys.verify(jwtToken, as: AppleIdentityToken.self)
let rv = UserCredentials(
email: payload.email ?? "",
bundleID: payload.audience.value.first ?? "app.bundle.id",
expiration: payload.expires.value
)
Log4swift[Self.self].info("payload.user: '\(rv)'")
Log4swift[Self.self].info("payload: '\(payload)'")
return rv
} catch {
Log4swift[Self.self].info("error: '\(error)'")
throw error
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment