ASWebAuthenticationSession redirect behavior inconsistent

250 Views Asked by At

We have a simple WKWebView for a website with multiple options to log in - Google, Microsoft and DLRG (German Life Saving Association). To authenticate, the auth url's are opened in a ASWebAuthenticationSession and the user can log in via the chosen method and returns to the website all logged in. The big problem is here, that the user is only logged-in in the popup. If I close the ASWebAuthenticationSession-popup, the user returns to the login page and can start from the beginning.

The even bigger problem is, that this is not consistent. Sometimes it works and the user is logged in after the ASWebAuthenticationSession process is finished, but sometimes the user is logged in in the popup. I have two phones to test, iPhone 11 and iPhone 13 mini, and on the iPhone 11 everything except for Google works every time and on the iPhone 13 sometimes it works, sometimes I'm logged into the popup. And this behavior is happening with the same TestFlight build with the same log-in data and when both devices are logged-out initially. It drives me crazy!

This is the relevant code:

session = ASWebAuthenticationSession(url: url, callbackURLScheme: "com.my.app") { callbackURL, error in
            if let error {
                print("We have an error authenticating: \(error.localizedDescription)")
                return
            }
            
            print("Session done and successfull")
        }
        
        session?.prefersEphemeralWebBrowserSession = true
        session?.presentationContextProvider = authenticationManager
        session?.start()

We have added the "Associated Domains" Capability with an "applinks" variable (so universal links), so the app will be opened when a user is tapping a link that contains our identifier. Because of this, the callbackURLScheme will be completely ignored (I have tried every possible variation with schemes in the info.plist) and the print statements are only accessed when dismissed via "cancel" button on the popup.

In the cases where it does work, I register a redirect url/uri coming from "outside" via this SwiftUI method:

.onOpenURL { url in
                webViewModel.openURL(url)
            }

Then I dismiss the popup (session), the url is loaded and the user is logged in. When it doesn't work, the .onOpenURL function doesn't register anything.


So it is working, but highly inconsistent. I thought maybe it's the backend functions that are wrong, but I talked to the person responsible for it and he explained the logic behind it and I think it's on the iOS side. Note that the browser or the Android app doesn't have any of these problems, so the redirect works. But if its on the iOS side, you can see there are only a few lines of code to customize the ASWebAuthenticationSession.

If you have any idea or leads, please let me know! Thank you! :)

Edit: This guy here seems to have the same problem, that it loads the content in the ASWebAuthenticationSession, and says it's due to the 2fa of Google. We use 2fa and have the same problem, but it's also happening with Microsoft for example without 2fa. So I'm not certain this has something to do with it.

1

There are 1 best solutions below

4
On

Maybe try this code, if you are using HTTPS redirect URLs. Call startLogin to invoke the window and endLogin when you receive the deep link response.

import Foundation
import AuthenticationServices

class LoginSession: NSObject, ASWebAuthenticationPresentationContextProviding {
    
    private var authSession: ASWebAuthenticationSession?
    private var error: OAuthError?
    
    func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
        return ASPresentationAnchor()
    }

    func startLogin(authorizationRequestUrl: String) throws {

        self.error = nil
        authSession = ASWebAuthenticationSession(
            url: URL(string: authorizationRequestUrl)!,
            callbackURLScheme: "https",
            completionHandler: { (callbackUrl: URL?, error: Error?) in

                defer {
                    self.authSession?.cancel()
                    self.authSession = nil
                }

                if let errorCode = (error as? NSError)?.code {
                    if errorCode != ASWebAuthenticationSessionError.Code.canceledLogin.rawValue {
                        self.error = OAuthError(code: "login_error", message: "ASWebAuthenticationSession error code \(errorCode)")
                    }
                }
            }
        )
     
        authSession?.presentationContextProvider = self
        authSession?.start()
    }

    func endLogin() {
        
        self.authSession?.cancel()
        self.authSession = nil
    }
}

Note that this code only deals with the login window. You will need to redeem the code for tokens separately, using the deep link response URL.

Note that you can't use a website to log into a mobile app. Eg if the website issues cookies the WKWebView will not share them with the above login window, since WKWebView is a private browsing session and uses a different cookie jar.

For more about iOS mobile logins using universal links you can run my code example. Note that there is a requirement for a user gesture for the login response to return reliably to the app. This is typically handled by some kind of interstitial page. My blog post provides further information on this.