openid / AppAuth-iOS

iOS and macOS SDK for communicating with OAuth 2.0 and OpenID Connect providers.
https://openid.github.io/AppAuth-iOS
Apache License 2.0
1.72k stars 745 forks source link

AppAuth-Logout from Okta and Identity Server4 in iOS mobile application. #284

Open guturiganesh opened 5 years ago

guturiganesh commented 5 years ago

I am using AppAuth library to support Okta and Identity Server4 authentication in my iOS application. I am able make user to login but couldn't able to make user to logout. I did not find much document on logout process. Can anyone please provide me some documents or code snippet on user logout.

WilliamDenniss commented 5 years ago

Logout support is still experimental and not included in our mainline releases. You can try out the feature from the dev-logout branch.

guturiganesh commented 5 years ago

Thanks for the update @WilliamDenniss Which functions I should call for Logout after taking dev-logout branch? any code snippet please. Also Can we use this branch for production app?

Hexfire commented 5 years ago

Any update on this? How can we perform logout with AppAuth?

BlueCorso commented 5 years ago

Hello, any chances to see this in master? @guturiganesh if you still need an answer: appDelegate.currentAuthorizationFlow = OIDAuthorizationService.present(request, externalUserAgent: agent, callback: { (response, error) in // handle response in case of success, remember to clear cookies

HTTPCookieStorage.shared.cookies?.forEach{ cookie in HTTPCookieStorage.shared.deleteCookie(cookie) } } where request is built using OIDEndSessionRequest(configuration: configuration, idTokenHint: idToken, postLogoutRedirectURL: redirectIRL, additionalParameters: [kLogOutID : kClientID]) and agent = OIDExternalUserAgentIOS(presenting: yourViewController)

idToken can be found on authState.

RanjeetStiga commented 5 years ago

Thanks for the update @WilliamDenniss Which functions I should call for Logout after taking dev-logout branch? any code snippet please. Also Can we use this branch for production app?

have you successfully logout .

BlueCorso commented 5 years ago

Thanks for the update @WilliamDenniss Which functions I should call for Logout after taking dev-logout branch? any code snippet please. Also Can we use this branch for production app?

have you successfully logout .

Yes it works.

RanjeetStiga commented 5 years ago

Thanks for the update @WilliamDenniss Which functions I should call for Logout after taking dev-logout branch? any code snippet please. Also Can we use this branch for production app?

have you successfully logout .

Yes it works.

which function have you call?

BlueCorso commented 5 years ago

Here's what I did:

Func logout (presenter: UIViewController) {
guard let authState = self.authState else {               
                return
            }
            guard let idToken = authState.lastTokenResponse?.idToken else {          
                return
            }
let request = OIDEndSessionRequest(configuration: authState.lastAuthorizationResponse.request.configuration,
                                               idTokenHint: idToken,
                                               postLogoutRedirectURL: Constants.RedirectURI,
                                               additionalParameters: nil)
            guard let agent = OIDExternalUserAgentIOS(presenting: presenter) else {
                return
            }
            self.currentAuthorizationFlow = OIDAuthorizationService.present(request, externalUserAgent: agent,
                                                                            callback: { (response, error) in
                if let response = response {
                    //delete cookies just in case
                    HTTPCookieStorage.shared.cookies?.forEach { cookie in
                        HTTPCookieStorage.shared.deleteCookie(cookie)
                    }
                    // successfully logout
                }
                if let err = error {
                   // print Error
                }    
            })
        }
}

Remember that you need to pass a viewcontroller (presenter) to handle the pop up (and of course your backend has to handle the logout request)

RanjeetStiga commented 5 years ago

Here's what I did:

Func logout (presenter: UIViewController) {
guard let authState = self.authState else {               
                return
            }
            guard let idToken = authState.lastTokenResponse?.idToken else {          
                return
            }
let request = OIDEndSessionRequest(configuration: authState.lastAuthorizationResponse.request.configuration,
                                               idTokenHint: idToken,
                                               postLogoutRedirectURL: Constants.RedirectURI,
                                               additionalParameters: nil)
            guard let agent = OIDExternalUserAgentIOS(presenting: presenter) else {
                return
            }
            self.currentAuthorizationFlow = OIDAuthorizationService.present(request, externalUserAgent: agent,
                                                                            callback: { (response, error) in
                if let response = response {
                    //delete cookies just in case
                    HTTPCookieStorage.shared.cookies?.forEach { cookie in
                        HTTPCookieStorage.shared.deleteCookie(cookie)
                    }
                    // successfully logout
                }
                if let err = error {
                   // print Error
                }    
            })
        }
}

Remember that you need to pass a viewcontroller (presenter) to handle the pop up (and of course your backend has to handle the logout request)

Thanks BlueCourso

But When i was delete the app and reinstall again then click on sign in the SDK give me pervious user (Google sign in). Please help me why pervious user come.

BlueCorso commented 5 years ago

The log out operation is server side... you still have to delete your authstate from your app (if you save it somewhere). If your backend is doing its job, the access token is no longer valid once you log out.

RanjeetStiga commented 5 years ago

The log out operation is server side... you still have to delete your authstate from your app (if you save it somewhere). If your backend is doing its job, the access token is no longer valid once you log out.

I have delete this but why same user return when delete this app.

RanjeetStiga commented 5 years ago

The log out operation is server side... you still have to delete your authstate from your app (if you save it somewhere). If your backend is doing its job, the access token is no longer valid once you log out.

I have delete this but why same user return when delete this app.

May be some cookies issue in iOS.

RanjeetStiga commented 5 years ago

The log out operation is server side... you still have to delete your authstate from your app (if you save it somewhere). If your backend is doing its job, the access token is no longer valid once you log out.

I have delete this but why same user return when delete this app.

May be some cookies issue in iOS.

OIDEndSessionRequest is not available in pod file.

BlueCorso commented 5 years ago

are pulling the correct branch? (dev-logout)

RanjeetStiga commented 5 years ago

are pulling the correct branch? (dev-logout)

I have add sdk via pod

BlueCorso commented 5 years ago

I use carthage but using pod you should have something like this in your podfile pod 'appAuth', :git => 'https://github.com/openid/AppAuth-iOS.git', :branch => 'dev-logout'

RanjeetStiga commented 5 years ago

I use carthage but using pod you should have something like this in your podfile pod 'appAuth', :git => 'https://github.com/openid/AppAuth-iOS.git', :branch => 'dev-logout'

I have pull dev-logout and add logout function but it show sign in pop message.

RanjeetStiga commented 5 years ago

Logout support is still experimental and not included in our mainline releases. You can try out the feature from the dev-logout branch.

when release proper logout feature in sdk

RanjeetStiga commented 5 years ago

I use carthage but using pod you should have something like this in your podfile pod 'appAuth', :git => 'https://github.com/openid/AppAuth-iOS.git', :branch => 'dev-logout'

I have pull dev-logout and add logout function but it show sign in pop message. Are you getting same sign in popup message when call logout function ?

BlueCorso commented 5 years ago

Yeah I get the annoying log in pop up for log out too. I hope they fix it soon.

balkarov commented 5 years ago

Hello. When you release this feature?

andrewvanbeek-okta commented 5 years ago

Hi all if you are looking to do this with Okta we actually have a newer sdk. It supports logout as well and is a wrapper around appauth. https://github.com/okta/okta-oidc-ios

arshadsk5 commented 4 years ago

Yeah I get the annoying log in pop up for log out too. I hope they fix it soon.

Even I am getting Sign In on logout alert popup, do anyone have solutions please. Any other alternative way to do this ? Please help. thanks in advance

nagasivaram-tadepalli commented 4 years ago

Hey, Have anyone solved the logout problem?

sethi-ishmeet commented 4 years ago

@arshadsk5 @BlueCorso Were you able to fix the issue? I'am also getting the login popup on logout.

GiulioVinci commented 4 years ago

@arshadsk5 @BlueCorso Were you able to fix the issue? I'am also getting the login popup on logout.

I am sorry, we migrated to active directory so I can't tell whether they fixed it or not :(

sethi-ishmeet commented 4 years ago

@gastonborba here is my logout function and it works perfectly fine. Another thing I needed to do was make sure I add PostLogoutRedirectURI in our Identity Server DB. If we don't do that, the server logs out, but user agent goes back to login page. After adding the post logout URI in DB, user agent automatically closes after logout completes.


func logout() {
        OIDAuthorizationService.discoverConfiguration(forIssuer: AppSettings.issuer) { configuration, error in

            guard let presentingViewController = self.presentingViewController else { return }

            guard let configuration = configuration else {
                fatalError("Error retrieving discovery document: \(error?.localizedDescription ?? "Unknown error")")
            }

            guard let idToken = self.authState?.lastTokenResponse?.idToken else { return }

            let request = OIDEndSessionRequest(configuration: configuration,
                                               idTokenHint: idToken,
                                               postLogoutRedirectURL: AppSettings.redirectUrl,
                                               additionalParameters: nil)

            guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
                return
            }

            guard let userAgent = OIDExternalUserAgentIOS(presenting: presentingViewController) else { return }

            appDelegate.currentAuthorizationFlow =
                OIDAuthorizationService.present(request,
                                                externalUserAgent: userAgent,
                                                callback: { [weak self] (_, _) in
                                                    guard let self = self else { return }
                                                    self.setAuthState(nil)
                })

        }
    }
sethi-ishmeet commented 4 years ago

Hey, Have anyone solved the logout problem?

@nagasivaram-tadepalli I have shared my answer above which works properly.

mmrobert commented 3 years ago

Here's what I did:

Func logout (presenter: UIViewController) {
guard let authState = self.authState else {               
                return
            }
            guard let idToken = authState.lastTokenResponse?.idToken else {          
                return
            }
let request = OIDEndSessionRequest(configuration: authState.lastAuthorizationResponse.request.configuration,
                                               idTokenHint: idToken,
                                               postLogoutRedirectURL: Constants.RedirectURI,
                                               additionalParameters: nil)
            guard let agent = OIDExternalUserAgentIOS(presenting: presenter) else {
                return
            }
            self.currentAuthorizationFlow = OIDAuthorizationService.present(request, externalUserAgent: agent,
                                                                            callback: { (response, error) in
                if let response = response {
                    //delete cookies just in case
                    HTTPCookieStorage.shared.cookies?.forEach { cookie in
                        HTTPCookieStorage.shared.deleteCookie(cookie)
                    }
                    // successfully logout
                }
                if let err = error {
                   // print Error
                }    
            })
        }
}

Remember that you need to pass a viewcontroller (presenter) to handle the pop up (and of course your backend has to handle the logout request)

My problem is that the agent (SFSafariViewController) show up and disappear quickly and the agent not stay. Any idea? Thanks.

dgoldman-pdx commented 2 years ago

@sethi-ishmeet I greatly appreciate your code example. But there are two parts of your answer that I don't understand, and have not been able to figure out for myself.

Another thing I needed to do was make sure I add PostLogoutRedirectURI in our Identity Server DB. If we don't do that, the server logs out, but user agent goes back to login page. After adding the post logout URI in DB, user agent automatically closes after logout completes.

Can you explain exactly what URI you added to your DB for the PostLogoutRedirectURI?

            let request = OIDEndSessionRequest(configuration: configuration,
                                               idTokenHint: idToken,
                                               postLogoutRedirectURL: AppSettings.redirectUrl,
                                               additionalParameters: nil)

What value did you use for AppSettings.redirectUrl?

Thanks for the further info!

sethi-ishmeet commented 2 years ago

@dgoldman-pdx

Both the redirectURI and PostLogoutRedirectURI are same as following. It's been a couple years since I worked on this app so I vaguely remember any details. Let me know if I can help in any other way.

dgoldman-pdx commented 2 years ago

@sethi-ishmeet thanks for the quick response!

Unfortunately, I'm still seeing the same thing as other posters, where the user agent reopens after logout, as if to attempt a new login. I'll post again here if I find a good solution/workaround. (Note: it appears that Okta has a workaround or two, but they're not trivial nor ideal.)

sethi-ishmeet commented 2 years ago

@dgoldman-pdx can you share your code? It might help investigate it.

dgoldman-pdx commented 2 years ago

@sethi-ishmeet my code is very similar to yours, above.

        guard let redirectUrl = URL(string: "\(bundleId):/oauth-callback") else { return }

            guard error == nil,
                  let tokenEndpoint = tokenEndpoint,
                  let authorizationEndpoint = authorizationEndpoint,
                  let logoutEndpoint = logoutEndpoint,
                  let idToken = authState.lastTokenResponse?.idToken,
                  let userAgent = OIDExternalUserAgentIOS(presenting: presentingViewController)
            else { return }

            DispatchQueue.main.async {
                print("Logging out via OpenID")

                let configuration = OIDServiceConfiguration(authorizationEndpoint: authorizationEndpoint, tokenEndpoint: tokenEndpoint, issuer: nil, registrationEndpoint: nil, endSessionEndpoint: logoutEndpoint)

                let logoutRequest = OIDEndSessionRequest(configuration: configuration,
                                                         idTokenHint: idToken,
                                                         postLogoutRedirectURL: redirectUrl,
                                                         additionalParameters: nil)

                // Note: need to retain a strong reference to the returned object until we're done with its SFAuthenticationSession
                appDelegate.currentAuthorizationFlow = OIDAuthorizationService.present(logoutRequest, externalUserAgent: userAgent) {
                    authorizationState, error in
                    print("OpenID logout: \(error?.localizedDescription ?? "SUCCESSFUL")")
                    appDelegate.currentAuthorizationFlow?.cancel()
                    appDelegate.currentAuthorizationFlow = nil
                }
            }

The redirectUrl is indeed one of the authorized redirect URIs set in our identity server (FusionAuth).

When this code executes, the user is logged out, but then AppAuth immediately displays the alert: {app} Wants to use {domain} to Sign In.

If the user taps Cancel, the alert is dismissed and the final print statement then displays:

OpenID logout: The operation couldn’t be completed. (org.openid.appauth.general error -3.)

If the user taps Continue, the alert is dismissed, then an empty modal dialog appears for an instant and dismisses itself, and the print statement displays:

OpenID logout: SUCCESSFUL
dgoldman-pdx commented 2 years ago

~Update: if I set redirectUrl to "", then (so far, at least) that seems to solve the problem of getting a log-in alert following logout. Which actually totally makes sense!~

Updated update: My mistake -- URL(string: "") actually returns nil, and OIDEndSessionRequest() does not accept nil for postLogoutRedirectURL.

dgoldman-pdx commented 2 years ago

Summary for the next person to deal with all of this...

  1. "Sign In" alert on logout:

  2. The The id_token_hint is invalid. error alert that sometimes appears on attempted logout:

    • One of my colleagues figured out that this happens when the id_token has expired. To avoid the error alert, before making the OIDEndSessionRequest, call authState.performAction(freshTokens:) to refresh the id_token.
tiwari1amrit commented 1 year ago

@sethi-ishmeet My solution is similar to your but the problem is that

OIDAuthorizationService.present(endSessionRequest, externalUserAgent: agent!) {response, error in Can retrieve response or error from this function.

this is my solution

            let authEndpoint = URL(string: Urls.Hydra.authEndpoint())!
            let tokenEndpoint = URL(string: Urls.Hydra.tokenEndpoint())!
            let redirectURL = URL(string: Urls.Hydra.redirectCallbackURL())!

            let logoutEndpointString = Urls.Hydra.logout() + "/?redirect_uri=" + Urls.Hydra.redirectCallbackURL()

            let logoutEndpoint = URL(string: logoutEndpointString)!

            let configuration = OIDServiceConfiguration(authorizationEndpoint: authEndpoint,
                                                        tokenEndpoint: tokenEndpoint,
                                                        issuer: nil,
                                                        registrationEndpoint: nil,
                                                        endSessionEndpoint: logoutEndpoint)

            guard let idToken = hydraAuthStateModel.idToken else {
                return
            }

            let endSessionRequest = OIDEndSessionRequest(configuration: configuration,
                                               idTokenHint: idToken,
                                               postLogoutRedirectURL: redirectURL,
                                               state: hydraAuthStateModel.state!,
                                               additionalParameters: nil)

            let agent = OIDExternalUserAgentIOS(presenting: viewController)

            OIDAuthorizationService.present(endSessionRequest,
                                            externalUserAgent: agent!) {response, error in

                if let error = error {
                    print("Authorization error: \(error.localizedDescription)")
                    return
                }

                guard let response = response else {
                    print("Authorization response is nil.")
                    return
                }

                print("Authorization response: \(response)")

                success?()
            }

Can you please help me to fix this issue?

sethi-ishmeet commented 1 year ago

@tiwariammit What exactly is the issue you are facing? I am not able to comprehend what's wrong in your code.

tiwari1amrit commented 1 year ago

@sethi-ishmeet thank you for your response. I successfully able to logout, but the issue is that after closing signout dialogue can'ta able to tack the completion of below function. OIDAuthorizationService.present(endSessionRequest, externalUserAgent: agent!) {response, error in The flow won't go inside block. So, can't able to track whether user logout successfully or not.