@@ -35,12 +35,28 @@ final class GoogleSignInAuthenticator: ObservableObject {
3535 print ( " There is no root view controller! " )
3636 return
3737 }
38-
39- GIDSignIn . sharedInstance. signIn ( withPresenting: rootViewController) { signInResult, error in
38+ let manualNonce = UUID ( ) . uuidString
39+
40+ GIDSignIn . sharedInstance. signIn (
41+ withPresenting: rootViewController,
42+ hint: nil ,
43+ additionalScopes: nil ,
44+ nonce: manualNonce
45+ ) { signInResult, error in
4046 guard let signInResult = signInResult else {
4147 print ( " Error! \( String ( describing: error) ) " )
4248 return
4349 }
50+
51+ // Per OpenID Connect Core section 3.1.3.7, rule #11, compare returned nonce to manual
52+ guard let idToken = signInResult. user. idToken? . tokenString,
53+ let returnedNonce = self . decodeNonce ( fromJWT: idToken) ,
54+ returnedNonce == manualNonce else {
55+ // Assert a failure for convenience so that integration tests with this sample app fail upon
56+ // `nonce` mismatch
57+ assertionFailure ( " ERROR: Returned nonce doesn't match manual nonce! " )
58+ return
59+ }
4460 self . authViewModel. state = . signedIn( signInResult. user)
4561 }
4662
@@ -125,3 +141,40 @@ final class GoogleSignInAuthenticator: ObservableObject {
125141 }
126142
127143}
144+
145+ // MARK: Parse nonce from JWT ID Token
146+
147+ private extension GoogleSignInAuthenticator {
148+ func decodeNonce( fromJWT jwt: String ) -> String ? {
149+ let segments = jwt. components ( separatedBy: " . " )
150+ guard let parts = decodeJWTSegment ( segments [ 1 ] ) ,
151+ let nonce = parts [ " nonce " ] as? String else {
152+ return nil
153+ }
154+ return nonce
155+ }
156+
157+ func decodeJWTSegment( _ segment: String ) -> [ String : Any ] ? {
158+ guard let segmentData = base64UrlDecode ( segment) ,
159+ let segmentJSON = try ? JSONSerialization . jsonObject ( with: segmentData, options: [ ] ) ,
160+ let payload = segmentJSON as? [ String : Any ] else {
161+ return nil
162+ }
163+ return payload
164+ }
165+
166+ func base64UrlDecode( _ value: String ) -> Data ? {
167+ var base64 = value
168+ . replacingOccurrences ( of: " - " , with: " + " )
169+ . replacingOccurrences ( of: " _ " , with: " / " )
170+
171+ let length = Double ( base64. lengthOfBytes ( using: String . Encoding. utf8) )
172+ let requiredLength = 4 * ceil( length / 4.0 )
173+ let paddingLength = requiredLength - length
174+ if paddingLength > 0 {
175+ let padding = " " . padding ( toLength: Int ( paddingLength) , withPad: " = " , startingAt: 0 )
176+ base64 = base64 + padding
177+ }
178+ return Data ( base64Encoded: base64, options: . ignoreUnknownCharacters)
179+ }
180+ }
0 commit comments