Skip to content

Commit

Permalink
feat: allow providing custom nonce
Browse files Browse the repository at this point in the history
  • Loading branch information
vonovak committed May 23, 2024
1 parent 4b4f14b commit 699a005
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 10 deletions.
27 changes: 27 additions & 0 deletions GoogleSignIn/Sources/GIDSignIn.m
Original file line number Diff line number Diff line change
Expand Up @@ -253,12 +253,25 @@ - (void)signInWithPresentingViewController:(UIViewController *)presentingViewCon
hint:(nullable NSString *)hint
additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
completion:(nullable GIDSignInCompletion)completion {
[self signInWithPresentingViewController:presentingViewController
hint:hint
additionalScopes:additionalScopes
nonce:nil
completion:completion];
}

- (void)signInWithPresentingViewController:(UIViewController *)presentingViewController
hint:(nullable NSString *)hint
additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
nonce:(nullable NSString *)nonce
completion:(nullable GIDSignInCompletion)completion {
GIDSignInInternalOptions *options =
[GIDSignInInternalOptions defaultOptionsWithConfiguration:_configuration
presentingViewController:presentingViewController
loginHint:hint
addScopesFlow:NO
scopes:additionalScopes
nonce:nonce
completion:completion];
[self signInWithOptions:options];
}
Expand Down Expand Up @@ -331,12 +344,25 @@ - (void)signInWithPresentingWindow:(NSWindow *)presentingWindow
hint:(nullable NSString *)hint
additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
completion:(nullable GIDSignInCompletion)completion {
[self signInWithPresentingWindow:presentingWindow
hint:hint
additionalScopes:additionalScopes
nonce:nil
completion:completion];
}

- (void)signInWithPresentingWindow:(NSWindow *)presentingWindow
hint:(nullable NSString *)hint
additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
nonce:(nullable NSString *)nonce
completion:(nullable GIDSignInCompletion)completion {
GIDSignInInternalOptions *options =
[GIDSignInInternalOptions defaultOptionsWithConfiguration:_configuration
presentingWindow:presentingWindow
loginHint:hint
addScopesFlow:NO
scopes:additionalScopes
nonce:nonce
completion:completion];
[self signInWithOptions:options];
}
Expand Down Expand Up @@ -598,6 +624,7 @@ - (void)authenticateInteractivelyWithOptions:(GIDSignInInternalOptions *)options
scopes:options.scopes
redirectURL:redirectURL
responseType:OIDResponseTypeCode
nonce:options.nonce
additionalParameters:additionalParameters];

_currentAuthorizationFlow = [OIDAuthorizationService
Expand Down
5 changes: 5 additions & 0 deletions GoogleSignIn/Sources/GIDSignInInternalOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ NS_ASSUME_NONNULL_BEGIN
/// The login hint to be used during the flow.
@property(nonatomic, copy, nullable) NSString *loginHint;

/// A cryptographically random value used to associate a Client session with an ID Token, and to mitigate replay attacks.
@property(nonatomic, copy, nullable) NSString *nonce;

/// Creates the default options.
#if TARGET_OS_IOS || TARGET_OS_MACCATALYST
+ (instancetype)defaultOptionsWithConfiguration:(nullable GIDConfiguration *)configuration
Expand All @@ -77,6 +80,7 @@ NS_ASSUME_NONNULL_BEGIN
loginHint:(nullable NSString *)loginHint
addScopesFlow:(BOOL)addScopesFlow
scopes:(nullable NSArray *)scopes
nonce:(nullable NSString *)nonce
completion:(nullable GIDSignInCompletion)completion;

#elif TARGET_OS_OSX
Expand All @@ -91,6 +95,7 @@ NS_ASSUME_NONNULL_BEGIN
loginHint:(nullable NSString *)loginHint
addScopesFlow:(BOOL)addScopesFlow
scopes:(nullable NSArray *)scopes
nonce:(nullable NSString *)nonce
completion:(nullable GIDSignInCompletion)completion;
#endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST

Expand Down
4 changes: 4 additions & 0 deletions GoogleSignIn/Sources/GIDSignInInternalOptions.m
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@ + (instancetype)defaultOptionsWithConfiguration:(nullable GIDConfiguration *)con
loginHint:(nullable NSString *)loginHint
addScopesFlow:(BOOL)addScopesFlow
scopes:(nullable NSArray *)scopes
nonce:(nullable NSString *)nonce
completion:(nullable GIDSignInCompletion)completion {
#elif TARGET_OS_OSX
+ (instancetype)defaultOptionsWithConfiguration:(nullable GIDConfiguration *)configuration
presentingWindow:(nullable NSWindow *)presentingWindow
loginHint:(nullable NSString *)loginHint
addScopesFlow:(BOOL)addScopesFlow
scopes:(nullable NSArray *)scopes
nonce:(nullable NSString *)nonce
completion:(nullable GIDSignInCompletion)completion {
#endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
GIDSignInInternalOptions *options = [[GIDSignInInternalOptions alloc] init];
Expand All @@ -54,6 +56,7 @@ + (instancetype)defaultOptionsWithConfiguration:(nullable GIDConfiguration *)con
options->_loginHint = loginHint;
options->_completion = completion;
options->_scopes = [GIDScopes scopesWithBasicProfile:scopes];
options->_nonce = nonce;
}
return options;
}
Expand All @@ -80,6 +83,7 @@ + (instancetype)defaultOptionsWithConfiguration:(nullable GIDConfiguration *)con
loginHint:loginHint
addScopesFlow:addScopesFlow
scopes:@[]
nonce:nil
completion:completion];
return options;
}
Expand Down
49 changes: 48 additions & 1 deletion GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,31 @@ typedef NS_ERROR_ENUM(kGIDSignInErrorDomain, GIDSignInErrorCode) {
NSError *_Nullable error))completion
NS_EXTENSION_UNAVAILABLE("The sign-in flow is not supported in App Extensions.");


/// Starts an interactive sign-in flow on iOS using the provided hint, additional scopes, and nonce.
///
/// The completion will be called at the end of this process. Any saved sign-in state will be
/// replaced by the result of this flow. Note that this method should not be called when the app is
/// starting up, (e.g in `application:didFinishLaunchingWithOptions:`); instead use the
/// `restorePreviousSignInWithCompletion:` method to restore a previous sign-in.
///
/// @param presentingViewController The view controller used to present `SFSafariViewController` on
/// iOS 9 and 10.
/// @param hint An optional hint for the authorization server, for example the user's ID or email
/// address, to be prefilled if possible.
/// @param additionalScopes An optional array of scopes to request in addition to the basic profile scopes.
/// @param nonce A custom nonce.
/// @param completion The optional block that is called on completion. This block will
/// be called asynchronously on the main queue.
- (void)signInWithPresentingViewController:(UIViewController *)presentingViewController
hint:(nullable NSString *)hint
additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
nonce:(nullable NSString *)nonce
completion:
(nullable void (^)(GIDSignInResult *_Nullable signInResult,
NSError *_Nullable error))completion
NS_EXTENSION_UNAVAILABLE("The sign-in flow is not supported in App Extensions.");

#elif TARGET_OS_OSX

/// Starts an interactive sign-in flow on macOS.
Expand Down Expand Up @@ -200,7 +225,7 @@ typedef NS_ERROR_ENUM(kGIDSignInErrorDomain, GIDSignInErrorCode) {
completion:(nullable void (^)(GIDSignInResult *_Nullable signInResult,
NSError *_Nullable error))completion;

/// Starts an interactive sign-in flow on macOS using the provided hint.
/// Starts an interactive sign-in flow on macOS using the provided hint and additional scopes.
///
/// The completion will be called at the end of this process. Any saved sign-in state will be
/// replaced by the result of this flow. Note that this method should not be called when the app is
Expand All @@ -219,6 +244,28 @@ typedef NS_ERROR_ENUM(kGIDSignInErrorDomain, GIDSignInErrorCode) {
completion:(nullable void (^)(GIDSignInResult *_Nullable signInResult,
NSError *_Nullable error))completion;

/// Starts an interactive sign-in flow on macOS using the provided hint, additional scopes, and nonce.
///
/// The completion will be called at the end of this process. Any saved sign-in state will be
/// replaced by the result of this flow. Note that this method should not be called when the app is
/// starting up, (e.g in `application:didFinishLaunchingWithOptions:`); instead use the
/// `restorePreviousSignInWithCompletion:` method to restore a previous sign-in.
///
/// @param presentingWindow The window used to supply `presentationContextProvider` for `ASWebAuthenticationSession`.
/// @param hint An optional hint for the authorization server, for example the user's ID or email
/// address, to be prefilled if possible.
/// @param additionalScopes An optional array of scopes to request in addition to the basic profile scopes.
/// @param nonce A custom nonce.
/// @param completion The optional block that is called on completion. This block will
/// be called asynchronously on the main queue.
- (void)signInWithPresentingWindow:(NSWindow *)presentingWindow
hint:(nullable NSString *)hint
additionalScopes:(nullable NSArray<NSString *> *)additionalScopes
nonce:(nullable NSString *)nonce
completion:(nullable void (^)(GIDSignInResult *_Nullable signInResult,
NSError *_Nullable error))completion;


#endif

@end
Expand Down
50 changes: 43 additions & 7 deletions GoogleSignIn/Tests/Unit/GIDSignInTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -426,8 +426,7 @@ - (void)testRestorePreviousSignInNoRefresh_hasPreviousUser {

// Mock generating a GIDConfiguration when initializing GIDGoogleUser.
OIDAuthorizationResponse *authResponse =
[OIDAuthorizationResponse testInstanceWithAdditionalParameters:nil
errorString:nil];
[OIDAuthorizationResponse testInstance];

OCMStub([_authState lastAuthorizationResponse]).andReturn(authResponse);
OCMStub([_tokenResponse idToken]).andReturn(kFakeIDToken);
Expand Down Expand Up @@ -602,7 +601,8 @@ - (void)testOAuthLogin_AdditionalScopes {
oldAccessToken:NO
modalCancel:NO
useAdditionalScopes:YES
additionalScopes:nil];
additionalScopes:nil
manualNonce:nil];

expectedScopeString = [@[ @"email", @"profile" ] componentsJoinedByString:@" "];
XCTAssertEqualObjects(_savedAuthorizationRequest.scope, expectedScopeString);
Expand All @@ -616,7 +616,8 @@ - (void)testOAuthLogin_AdditionalScopes {
oldAccessToken:NO
modalCancel:NO
useAdditionalScopes:YES
additionalScopes:@[ kScope ]];
additionalScopes:@[ kScope ]
manualNonce:nil];

expectedScopeString = [@[ kScope, @"email", @"profile" ] componentsJoinedByString:@" "];
XCTAssertEqualObjects(_savedAuthorizationRequest.scope, expectedScopeString);
Expand All @@ -630,7 +631,8 @@ - (void)testOAuthLogin_AdditionalScopes {
oldAccessToken:NO
modalCancel:NO
useAdditionalScopes:YES
additionalScopes:@[ kScope, kScope2 ]];
additionalScopes:@[ kScope, kScope2 ]
manualNonce:nil];

expectedScopeString = [@[ kScope, kScope2, @"email", @"profile" ] componentsJoinedByString:@" "];
XCTAssertEqualObjects(_savedAuthorizationRequest.scope, expectedScopeString);
Expand Down Expand Up @@ -722,6 +724,35 @@ - (void)testOpenIDRealm {
XCTAssertEqual(params[kOpenIDRealmKey], kOpenIDRealm, @"OpenID Realm should match.");
}

- (void)testManualNonce {
_signIn.configuration = [[GIDConfiguration alloc] initWithClientID:kClientId
serverClientID:nil
hostedDomain:nil
openIDRealm:kOpenIDRealm];

OCMStub(
[_keychainStore saveAuthSession:OCMOCK_ANY error:OCMArg.anyObjectRef]
).andDo(^(NSInvocation *invocation) {
self->_keychainSaved = self->_saveAuthorizationReturnValue;
});

NSString* manualNonce = @"manual_nonce";

[self OAuthLoginWithAddScopesFlow:NO
authError:nil
tokenError:nil
emmPasscodeInfoRequired:NO
keychainError:NO
restoredSignIn:NO
oldAccessToken:NO
modalCancel:NO
useAdditionalScopes:NO
additionalScopes:@[]
manualNonce:manualNonce];

XCTAssertEqualObjects(_savedAuthorizationRequest.nonce, manualNonce, @"nonce provided to signInWithPresenting[ViewController/Window] should be the same as the one in the authorization request.");
}

- (void)testOAuthLogin_LoginHint {
_hint = kUserEmail;

Expand Down Expand Up @@ -1301,7 +1332,8 @@ - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow
oldAccessToken:oldAccessToken
modalCancel:modalCancel
useAdditionalScopes:NO
additionalScopes:nil];
additionalScopes:nil
manualNonce:nil];
}

// The authorization flow with parameters to control which branches to take.
Expand All @@ -1314,7 +1346,8 @@ - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow
oldAccessToken:(BOOL)oldAccessToken
modalCancel:(BOOL)modalCancel
useAdditionalScopes:(BOOL)useAdditionalScopes
additionalScopes:(NSArray *)additionalScopes {
additionalScopes:(NSArray *)additionalScopes
manualNonce:(NSString*)nonce {
if (restoredSignIn) {
// clearAndAuthenticateWithOptions
[[[_authorization expect] andReturn:_authState] authState];
Expand All @@ -1326,6 +1359,7 @@ - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow
@{ @"emm_passcode_info_required" : @"1" } : nil;
OIDAuthorizationResponse *authResponse =
[OIDAuthorizationResponse testInstanceWithAdditionalParameters:additionalParameters
nonce:nonce
errorString:authError];

OIDTokenResponse *tokenResponse =
Expand Down Expand Up @@ -1401,6 +1435,8 @@ - (void)OAuthLoginWithAddScopesFlow:(BOOL)addScopesFlow
[_signIn signInWithPresentingWindow:_presentingWindow
#endif // TARGET_OS_IOS || TARGET_OS_MACCATALYST
hint:_hint
additionalScopes:nil
nonce:nonce
completion:completion];
}
}
Expand Down
2 changes: 2 additions & 0 deletions GoogleSignIn/Tests/Unit/OIDAuthorizationRequest+Testing.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,6 @@ extern NSString *const OIDAuthorizationRequestTestingCodeVerifier;

+ (instancetype)testInstance;

+ (instancetype)testInstanceWithNonce:(nullable NSString *)nonce;

@end
5 changes: 5 additions & 0 deletions GoogleSignIn/Tests/Unit/OIDAuthorizationRequest+Testing.m
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,18 @@
@implementation OIDAuthorizationRequest (Testing)

+ (instancetype)testInstance {
return [self testInstanceWithNonce:nil];
}

+ (instancetype)testInstanceWithNonce:(nullable NSString *)nonce {
return [[OIDAuthorizationRequest alloc]
initWithConfiguration:[OIDServiceConfiguration testInstance]
clientId:OIDAuthorizationRequestTestingClientID
scopes:@[ OIDAuthorizationRequestTestingScope,
OIDAuthorizationRequestTestingScope2 ]
redirectURL:[NSURL URLWithString:@"http://test.com"]
responseType:OIDResponseTypeCode
nonce:nonce
additionalParameters:nil];
}

Expand Down
1 change: 1 addition & 0 deletions GoogleSignIn/Tests/Unit/OIDAuthorizationResponse+Testing.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

+ (instancetype)testInstanceWithAdditionalParameters:
(NSDictionary<NSString *, NSString *> *)additionalParameters
nonce:(NSString*) nonce
errorString:(NSString *)errorString;

@end
5 changes: 3 additions & 2 deletions GoogleSignIn/Tests/Unit/OIDAuthorizationResponse+Testing.m
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@
@implementation OIDAuthorizationResponse (Testing)

+ (instancetype)testInstance {
return [self testInstanceWithAdditionalParameters:nil errorString:nil];
return [self testInstanceWithAdditionalParameters:nil nonce:nil errorString:nil];
}

+ (instancetype)testInstanceWithAdditionalParameters:
(NSDictionary<NSString *, NSString *> *)additionalParameters
nonce:(NSString *)nonce
errorString:(NSString *)errorString {

NSMutableDictionary<NSString *, NSString *> *parameters;
Expand All @@ -45,7 +46,7 @@ + (instancetype)testInstanceWithAdditionalParameters:
[parameters addEntriesFromDictionary:additionalParameters];
}
}
return [[OIDAuthorizationResponse alloc] initWithRequest:[OIDAuthorizationRequest testInstance]
return [[OIDAuthorizationResponse alloc] initWithRequest:[OIDAuthorizationRequest testInstanceWithNonce:nonce]
parameters:parameters];
}

Expand Down

0 comments on commit 699a005

Please sign in to comment.