Last active
October 22, 2024 04:40
-
-
Save kdeda/b49ba9f2b9b1a400a17b5fae2039cbbc to your computer and use it in GitHub Desktop.
Verifying a Sign in with Apple JWT in Vapor 4
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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