How to Sign Out of Google After Being Authenticated
Asked Answered
A

6

31

So my app has the option to sign in with Google. Upon clicking the button that Google provides, a web view opens and has the user input their credentials. After allowing the app to access their information the app then signs the user in and changes the SignInViewController to the TabBarController (where they can now interact accordingly).

When the user presses a Signout button they are directed to the login screen as one would expect. But the odd thing is, if the user presses the google button again they are automatically signed in with no further authentication at all and no option to remove their account. Is their a way to clear the google account credentials as to protect the users from accidental theft?

Sign in function:

func signIn(signIn: GIDSignIn!, didSignInForUser user: GIDGoogleUser!, withError error: NSError!) {
    if let error = error {
        print(error.localizedDescription)
        return
    }
    let authentication = user.authentication
    let credential = FIRGoogleAuthProvider.credentialWithIDToken(authentication.idToken, accessToken: authentication.accessToken)
    FIRAuth.auth()?.signInWithCredential(credential) { (user, error) in
        // ...
        SignInViewController().signedIn(user)
    }
    // ...
}

Sign out function:

func signOutOverride() {
    do {
        try! FIRAuth.auth()!.signOut()
        CredentialState.sharedInstance.signedIn = false
        // Set the view to the login screen after signing out
        let storyboard = UIStoryboard(name: "SignIn", bundle: nil)
        let loginVC = storyboard.instantiateViewControllerWithIdentifier("SignInVC") as! SignInViewController
        let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
        appDelegate.window?.rootViewController = loginVC
    } catch let signOutError as NSError {
        print ("Error signing out: \(signOutError)")
    }
}
Ahriman answered 21/6, 2016 at 5:55 Comment(1)
have you tried GIDSignIn.sharedInstance().signOut()Recognizee
R
54

Swift

try GIDSignIn.sharedInstance().signOut()

objective - c

[[GIDSignIn sharedInstance] signOut];
Recognizee answered 21/6, 2016 at 6:42 Comment(1)
This doesn't work. It removes the session login, but still keeps the users authentication information cached. If you click on SignIn again, you will see the user bypasses password input.Valvulitis
L
14

Yes, like @Rahul said following code would be the right way of going about it.

GIDSignIn.sharedInstance().signOut()

https://developers.google.com/identity/sign-in/ios/sign-in?ver=swift#sign_out_the_user

Lockout answered 21/6, 2016 at 6:25 Comment(0)
D
4

Wanted to elaborate a bit on the previous answers after playing with the GoogleSignIn SDK.

I saw the signOut() and disconnect() methods and was wondering what the differences were.

signOut() is a synchronous call:

// Immediately sets GIDSignIn.sharedInstance()?.currentUser to nil. 
// For example, if the user is already signed in:

print(GIDSignIn.sharedInstance()?.currentUser != nil) // true - signed in
GIDSignIn.sharedInstance()?.signOut()
print(GIDSignIn.sharedInstance()?.currentUser != nil) // false - signed out

disconnect() allows a user to revoke access to the app in addition to logging out. I assume this means they'll need to re-grant any permissions to your app if they choose to log in again.

According to Google's Developer Docs if a user chooses to disconnect from your app, then you'll need to remove any of the user's Google data that has been stored in your app.

Also, disconnect() is asynchronous. The result of the disconnect call will be returned to the GIDSignInDelegate.sign(_:didDisconnectWith:withError:) method.

// Also sets GIDSignIn.sharedInstance()?.currentUser to nil. 
// Asynchronous call. If for example the user was already signed in:

print(GIDSignIn.sharedInstance()?.currentUser != nil) // true - signed in
GIDSignIn.sharedInstance()?.disconnect()
print(GIDSignIn.sharedInstance()?.currentUser != nil) // true - still signed in

// MARK: - GIDSignInDelegate
func sign(_ signIn: GIDSignIn!, didDisconnectWith user: GIDGoogleUser!, withError error: Error!) {
    print(GIDSignIn.sharedInstance()?.currentUser != nil) // false - signed out

    // Remove any data saved to your app obtained from Google's APIs for this user.
}
Dorris answered 31/12, 2018 at 6:41 Comment(0)
M
4

Why doesn't the user need to renter their password after signing out?

I wanted to give some clarity to why the user instantly signs back in and why some of these solutions don't work on iOS 13.

The following does in fact sign the user out of your app:

GIDSignIn.sharedInstance().signOut()

But it does not sign the user out from google in Safari itself!! Try this for yourself, sign into a google account in your app. Sign out from the app and then go to accounts.google.com in your Safari browser, the account will still be signed in! So the cookies are being shared with the default Safari browser but why?

After investigating a little bit I found this in the OIDExternalUserAgentiOS class that handles the actual interactive auth flow.

  // iOS 12 and later, use ASWebAuthenticationSession
  if (@available(iOS 12.0, *)) {
    // ASWebAuthenticationSession doesn't work with guided access (rdar://40809553)
    if (!UIAccessibilityIsGuidedAccessEnabled()) {
      __weak OIDExternalUserAgentIOS *weakSelf = self;
      NSString *redirectScheme = request.redirectScheme;
      ASWebAuthenticationSession *authenticationVC =
          [[ASWebAuthenticationSession alloc] initWithURL:requestURL
                                        callbackURLScheme:redirectScheme
                                        completionHandler:^(NSURL * _Nullable callbackURL,
                                                            NSError * _Nullable error) {
        __strong OIDExternalUserAgentIOS *strongSelf = weakSelf;
        if (!strongSelf) {
            return;
        }
        strongSelf->_webAuthenticationVC = nil;
        if (callbackURL) {
          [strongSelf->_session resumeExternalUserAgentFlowWithURL:callbackURL];
        } else {
          NSError *safariError =
              [OIDErrorUtilities errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow
                               underlyingError:error
                                   description:nil];
          [strongSelf->_session failExternalUserAgentFlowWithError:safariError];
        }
      }];
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000
      if (@available(iOS 13.0, *)) {
          authenticationVC.presentationContextProvider = self;
      }
#endif
      _webAuthenticationVC = authenticationVC;
      openedUserAgent = [authenticationVC start];
    }
  }
  // iOS 11, use SFAuthenticationSession
  if (@available(iOS 11.0, *)) {
    // SFAuthenticationSession doesn't work with guided access (rdar://40809553)
    if (!openedUserAgent && !UIAccessibilityIsGuidedAccessEnabled()) {
      __weak OIDExternalUserAgentIOS *weakSelf = self;
      NSString *redirectScheme = request.redirectScheme;
      SFAuthenticationSession *authenticationVC =
          [[SFAuthenticationSession alloc] initWithURL:requestURL
                                     callbackURLScheme:redirectScheme
                                     completionHandler:^(NSURL * _Nullable callbackURL,
                                                         NSError * _Nullable error) {
        __strong OIDExternalUserAgentIOS *strongSelf = weakSelf;
        if (!strongSelf) {
            return;
        }
        strongSelf->_authenticationVC = nil;
        if (callbackURL) {
          [strongSelf->_session resumeExternalUserAgentFlowWithURL:callbackURL];
        } else {
          NSError *safariError =
              [OIDErrorUtilities errorWithCode:OIDErrorCodeUserCanceledAuthorizationFlow
                               underlyingError:error
                                   description:@"User cancelled."];
          [strongSelf->_session failExternalUserAgentFlowWithError:safariError];
        }
      }];
      _authenticationVC = authenticationVC;
      openedUserAgent = [authenticationVC start];
    }
  }
  // iOS 9 and 10, use SFSafariViewController
  if (@available(iOS 9.0, *)) {
    if (!openedUserAgent && _presentingViewController) {
      SFSafariViewController *safariVC =
          [[SFSafariViewController alloc] initWithURL:requestURL];
      safariVC.delegate = self;
      _safariVC = safariVC;
      [_presentingViewController presentViewController:safariVC animated:YES completion:nil];
      openedUserAgent = YES;
    }
  }
  // iOS 8 and earlier, use mobile Safari
  if (!openedUserAgent){
    openedUserAgent = [[UIApplication sharedApplication] openURL:requestURL];
  }

If you are on iOS 12+ it uses ASWebAuthenticationService, which shares cookies with Safari by default! For iOS 11, 10, 9, 8 it's a similar situation with different execution methods. Also something interesting I found in Apple documentation here about SFSafariViewController:

In iOS 9 and 10, it [SFSafariViewController] shares cookies and other website data with Safari.

When the google sign in page appears, it shares cookies with Safari. This is why we aren't being signed out of google completely. In fact, this seems to be completely intentional.

How do we sign out the user?

This works for all iOS versions that GIDSignIn supports:

let url = URL(string: "https://accounts.google.com/Logout")!
UIApplication.shared.open(url, options: [:], completion: nil)

Unfortunately this redirects the user outside the app. But you can explain the necessity in an UIAlertController before calling open.

Monazite answered 25/1, 2020 at 5:31 Comment(0)
J
2
  public func logOut(on:UIViewController){

    let firebaseAuth = Auth.auth()
    do {
        try  firebaseAuth.signOut()
            GIDSignIn.sharedInstance().signOut()
            GIDSignIn.sharedInstance().disconnect()

        if let url = NSURL(string:  "https://www.google.com/accounts/Logout?continue=https://appengine.google.com/_ah/logout?continue=https://google.com"){
            UIApplication.shared.open(url as URL, options: [:]) { (true) in
                let appDel:AppDelegate = UIApplication.shared.delegate as! AppDelegate
                appDel.window?.rootViewController = LoginViewController()
            }
        }
    } catch let signOutError as NSError {
        Help.shared.Snack(messageString: "Error signing out: \(signOutError)" 
)
        print ("Error signing out: %@", signOutError)
    }
}
Jenifer answered 4/3, 2019 at 15:58 Comment(1)
My account was stuck in a 400 error and calling signOut or disconnect didn't log out. Hitting the Logout link in mobile Safari, or the SafariViewController fixed the issue.Progressionist
H
0

if anyone is still looking at this i think i have this working a the OP originally asked.

So in my case i did something like this:

GIDSignIn *gidObject = [GIDSignIn sharedInstance];
[gidObject signOut];
[gidObject disconnect];


NSString *logOutUrl = @"https://www.google.com/accounts/Logout";
[[UIApplication sharedApplication] openURL:[NSURL URLWithString: logOutUrl] options:@{} completionHandler:nil];

I had the application go to the google logout url as seen above. For our workflow i wanted the user to actively know they are logging out. Kinda like a check for the user. What i had wrong is the signOut and disconnect of GIDSignIn. Before I had the signOut proceed after disconnect. When I had it that way no matter what the user did they were never "signed out" of google. When I reversed disconnect and signOut it logs the user out of their google account, which is what we wanted.

I guess logically one would signOut first before disconnecting from the app.

Hulburt answered 10/2, 2020 at 18:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.