|
|
@@ -35,12 +35,28 @@ final class GoogleSignInAuthenticator: ObservableObject {
|
|
|
print("There is no root view controller!")
|
|
|
return
|
|
|
}
|
|
|
-
|
|
|
- GIDSignIn.sharedInstance.signIn(withPresenting: rootViewController) { signInResult, error in
|
|
|
+ let manualNonce = UUID().uuidString
|
|
|
+
|
|
|
+ GIDSignIn.sharedInstance.signIn(
|
|
|
+ withPresenting: rootViewController,
|
|
|
+ hint: nil,
|
|
|
+ additionalScopes: nil,
|
|
|
+ nonce: manualNonce
|
|
|
+ ) { signInResult, error in
|
|
|
guard let signInResult = signInResult else {
|
|
|
print("Error! \(String(describing: error))")
|
|
|
return
|
|
|
}
|
|
|
+
|
|
|
+ // Per OpenID Connect Core section 3.1.3.7, rule #11, compare returned nonce to manual
|
|
|
+ guard let idToken = signInResult.user.idToken?.tokenString,
|
|
|
+ let returnedNonce = self.decodeNonce(fromJWT: idToken),
|
|
|
+ returnedNonce == manualNonce else {
|
|
|
+ // Assert a failure for convenience so that integration tests with this sample app fail upon
|
|
|
+ // `nonce` mismatch
|
|
|
+ assertionFailure("ERROR: Returned nonce doesn't match manual nonce!")
|
|
|
+ return
|
|
|
+ }
|
|
|
self.authViewModel.state = .signedIn(signInResult.user)
|
|
|
}
|
|
|
|
|
|
@@ -125,3 +141,40 @@ final class GoogleSignInAuthenticator: ObservableObject {
|
|
|
}
|
|
|
|
|
|
}
|
|
|
+
|
|
|
+// MARK: Parse nonce from JWT ID Token
|
|
|
+
|
|
|
+private extension GoogleSignInAuthenticator {
|
|
|
+ func decodeNonce(fromJWT jwt: String) -> String? {
|
|
|
+ let segments = jwt.components(separatedBy: ".")
|
|
|
+ guard let parts = decodeJWTSegment(segments[1]),
|
|
|
+ let nonce = parts["nonce"] as? String else {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ return nonce
|
|
|
+ }
|
|
|
+
|
|
|
+ func decodeJWTSegment(_ segment: String) -> [String: Any]? {
|
|
|
+ guard let segmentData = base64UrlDecode(segment),
|
|
|
+ let segmentJSON = try? JSONSerialization.jsonObject(with: segmentData, options: []),
|
|
|
+ let payload = segmentJSON as? [String: Any] else {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ return payload
|
|
|
+ }
|
|
|
+
|
|
|
+ func base64UrlDecode(_ value: String) -> Data? {
|
|
|
+ var base64 = value
|
|
|
+ .replacingOccurrences(of: "-", with: "+")
|
|
|
+ .replacingOccurrences(of: "_", with: "/")
|
|
|
+
|
|
|
+ let length = Double(base64.lengthOfBytes(using: String.Encoding.utf8))
|
|
|
+ let requiredLength = 4 * ceil(length / 4.0)
|
|
|
+ let paddingLength = requiredLength - length
|
|
|
+ if paddingLength > 0 {
|
|
|
+ let padding = "".padding(toLength: Int(paddingLength), withPad: "=", startingAt: 0)
|
|
|
+ base64 = base64 + padding
|
|
|
+ }
|
|
|
+ return Data(base64Encoded: base64, options: .ignoreUnknownCharacters)
|
|
|
+ }
|
|
|
+}
|