Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 21 additions & 12 deletions GoogleSignIn/Sources/GIDSignIn.m
Original file line number Diff line number Diff line change
Expand Up @@ -726,14 +726,23 @@ - (void)authorizationRequestWithOptions:(GIDSignInInternalOptions *)options comp
- (OIDAuthorizationRequest *)
authorizationRequestWithOptions:(GIDSignInInternalOptions *)options
additionalParameters:(NSDictionary<NSString *, NSString *> *)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;
}

Expand Down Expand Up @@ -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;

Expand Down
5 changes: 3 additions & 2 deletions GoogleSignIn/Tests/Unit/GIDSignInTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand Down Expand Up @@ -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];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down Expand Up @@ -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)
}
}