4

I would like to authenticate against an OAuth API usingASWebAuthenticationSession however it doesn't seem to be usable from SwiftUI. This is what I would like to have:

struct ContentView: View: ASWebAuthenticationPresentationContextProviding {
    var body: some View {
        NavigationView {
            VStack {
                Button("Hello World", {
                    // Run oauth flow
                }
            }
        }
        .navigationBarTitle(Text("Greed of Savin"))
        .navigationViewStyle(StackNavigationViewStyle())
    }

    func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
        return BungieApi.sharedInstance.presentationAnchor ?? ASPresentationAnchor()
    }
}

}

It requires adopting the protocol ASWebAuthenticationPresentationContextProviding which is not compatible with SwiftUI's Views.

I can get past this by redirecting to a ViewController that can then provides the ASWebAuthenticationPresentationContextProviding, but that adds an additional view/navigation step.

Is there any way to use ASWebAuthenticationSession from SwiftUI without dropping into a ViewController?

chustar
  • 12,225
  • 24
  • 81
  • 119

2 Answers2

4

I solved this in three parts:

First, capture the window in a global object during setup in SceneDelegate.swift:

var globalPresentationAnchor: ASPresentationAnchor? = nil
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // ...            
        globalPresentationAnchor = window
    }
}

Then, create a small ViewController to provide that window object to the using ASWebAuthenticationSession:

class ShimViewController: UIViewController, ASWebAuthenticationPresentationContextProviding
{
    func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
        // Perhaps I don't need the window object at all, and can just use:
        // return ASPresentationAnchor()
        return globalPresentationAnchor ?? ASPresentationAnchor()
    }
}

Finally, call the authentication API, providing the ShimViewController as the presenter.

    let session = ASWebAuthenticationSession(/**/)
    session.presentationContextProvider = ShimViewController()
    session.start()
chustar
  • 12,225
  • 24
  • 81
  • 119
  • 1
    Hi, I am getting the following warning `Instance will be immediately deallocated because property 'presentationContextProvider' is 'weak'` at `session.presentationContextProvider = ShimViewController()`. Any idea why? Thanks heaps already for the answer. – Varundroid Apr 20 '20 at 10:22
  • @Varundroid according to [the documentation](https://developer.apple.com/documentation/authenticationservices/authenticating_a_user_through_a_web_service): > In macOS, or if you have a deployment target of iOS 13 or later, the session keeps a strong reference to itself until the authentication process completes to prevent the system from deallocating the closure. For earlier iOS deployment targets, your app needs to keep a strong reference to the session until authentication completes. – Zoltan Aug 30 '20 at 10:51
  • I can confirm that this works perfectly on iOS 14. The globalPresentationAnchor is not even necessary – Zoltan Aug 30 '20 at 11:23
  • just returning `ASPresentationAnchor()` seems to be the best approach for swiftui – atultw Dec 31 '22 at 07:01
0

Using BetterSafariView, it is possible to use ASWebAuthenticationSession without hooking SceneDelegate in SwiftUI.

import SwiftUI
import BetterSafariView

struct ContentView: View {
    
    @State private var startingWebAuthenticationSession = false
    
    var body: some View {
        Button("Start WebAuthenticationSession") {
            self.startingWebAuthenticationSession = true
        }
        .webAuthenticationSession(isPresented: $startingWebAuthenticationSession) {
            WebAuthenticationSession(
                url: URL(string: "https://github.com/login/oauth/authorize?client_id=\(clientID)")!,
                callbackURLScheme: "myapp"
            ) { callbackURL, error in
                print(callbackURL, error)
            }
        }
    }
}
Stleamist
  • 193
  • 2
  • 12