diff --git a/GoogleSignIn/Sources/GIDSignIn.m b/GoogleSignIn/Sources/GIDSignIn.m index f1187ff1..093e33d6 100644 --- a/GoogleSignIn/Sources/GIDSignIn.m +++ b/GoogleSignIn/Sources/GIDSignIn.m @@ -726,14 +726,23 @@ - (void)authorizationRequestWithOptions:(GIDSignInInternalOptions *)options comp - (OIDAuthorizationRequest *) authorizationRequestWithOptions:(GIDSignInInternalOptions *)options additionalParameters:(NSDictionary *)additionalParameters { - OIDAuthorizationRequest *request = - [[OIDAuthorizationRequest alloc] initWithConfiguration:_appAuthConfiguration - clientId:options.configuration.clientID - scopes:options.scopes - redirectURL:[self redirectURLWithOptions:options] - responseType:OIDResponseTypeCode - nonce:options.nonce - additionalParameters:additionalParameters]; + OIDAuthorizationRequest *request; + if (options.nonce) { + request = [[OIDAuthorizationRequest alloc] initWithConfiguration:_appAuthConfiguration + clientId:options.configuration.clientID + scopes:options.scopes + redirectURL:[self redirectURLWithOptions:options] + responseType:OIDResponseTypeCode + nonce:options.nonce + additionalParameters:additionalParameters]; + } else { + request = [[OIDAuthorizationRequest alloc] initWithConfiguration:_appAuthConfiguration + clientId:options.configuration.clientID + scopes:options.scopes + redirectURL:[self redirectURLWithOptions:options] + responseType:OIDResponseTypeCode + additionalParameters:additionalParameters]; + } return request; } @@ -918,10 +927,10 @@ - (void)maybeFetchToken:(GIDAuthFlow *)authFlow { } [authFlow wait]; - [OIDAuthorizationService - performTokenRequest:tokenRequest - callback:^(OIDTokenResponse *_Nullable tokenResponse, - NSError *_Nullable error) { + [OIDAuthorizationService performTokenRequest:tokenRequest + originalAuthorizationResponse:authFlow.authState.lastAuthorizationResponse + callback:^(OIDTokenResponse *_Nullable tokenResponse, + NSError *_Nullable error) { [authState updateWithTokenResponse:tokenResponse error:error]; authFlow.error = error; diff --git a/GoogleSignIn/Tests/Unit/GIDSignInTest.m b/GoogleSignIn/Tests/Unit/GIDSignInTest.m index 6bdd19d1..7d5195d8 100644 --- a/GoogleSignIn/Tests/Unit/GIDSignInTest.m +++ b/GoogleSignIn/Tests/Unit/GIDSignInTest.m @@ -297,7 +297,7 @@ - (void)setUp { #elif TARGET_OS_OSX _presentingWindow = OCMStrictClassMock([NSWindow class]); #endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST - _authState = OCMStrictClassMock([OIDAuthState class]); + _authState = OCMClassMock([OIDAuthState class]); OCMStub([_authState alloc]).andReturn(_authState); OCMStub([_authState initWithAuthorizationResponse:OCMOCK_ANY]).andReturn(_authState); _tokenResponse = OCMStrictClassMock([OIDTokenResponse class]); @@ -327,7 +327,8 @@ - (void)setUp { callback:COPY_TO_ARG_BLOCK(self->_savedAuthorizationCallback)]); OCMStub([self->_oidAuthorizationService performTokenRequest:SAVE_TO_ARG_BLOCK(self->_savedTokenRequest) - callback:COPY_TO_ARG_BLOCK(self->_savedTokenCallback)]); + originalAuthorizationResponse:[OCMArg any] + callback:COPY_TO_ARG_BLOCK(self->_savedTokenCallback)]); // Fakes _fetcherService = [[GIDFakeFetcherService alloc] init]; diff --git a/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift b/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift index deacb35b..de227f08 100644 --- a/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift +++ b/Samples/Swift/DaysUntilBirthday/Shared/Services/GoogleSignInAuthenticator.swift @@ -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) + } +}