AuthenticationServices - is it completely broken on Catalyst?

I have a SwiftUI app live on the App Store which uses ASWebAuthenticationSession to authenticate against several remote services. It all runs perfectly on iOS but I'm getting a stream of complaints from users running it on Monterey / Catalyst. There seem to be 2 main errors:

  1. The auth browser window doesn't anchor properly, so the window will pop-up but it's completely independent of my app, so can easily end-up behind my application (which then appears to have hung)
  2. Even worse, on some machines (mostly m1 iMacs) the window doesn't pop-up at all but the OAuth request to the browser sits in some queue somewhere and at a later point when the user happens to restart their browser they will be prompted to login for every single time they clicked on the "sign in" button in my app.

I've seen lots of other reports of the 2nd problem which just seems to happen randomly so I don't have a repro. I've seen a similar number of different ways of implementing ASWebAuthenticationPresentationContextProviding which (I presume) ought to fix the former. Unfortunately none of them work.

I'm including some minimal code to reproduce the former issue. This is my own amalgamation of several other approaches. It doesn't work - if you click on the "sign in" button the OAuth window will pop-up but it's completely independent and you can easily move the "anchor" window on top of it.

Has anyone managed to get this working?

Here's the code:

import SwiftUI
import AuthenticationServices
import UIKit

struct SignInView: View {
    @StateObject var viewModel = SignInViewModel()
    @State var window: UIWindow? = nil

    var body: some View {
        VStack(spacing: 16) {
            Image(systemName: "person.circle")
                .resizable()
                .frame(width: 50, height: 50)
                .foregroundColor(.primary)
            VStack(spacing: 8) {
                Text("You must be log in to proceed any further")
                    .foregroundColor(.secondary)
                    .font(.title3)
                    .multilineTextAlignment(.center)
                    .padding()
                Button {
                    viewModel.signIn(window: self.window)
                } label: {
                    Text("Sign In")
                        .foregroundColor(.white)
                        .padding()
                        .clipShape(RoundedRectangle(cornerRadius: 8))
                        .background(
                                    HostingWindowFinder { window in
                                        self.window = window
                                   }
                        )
                }
            }
        }
    }
}

class SignInViewModel: NSObject, ObservableObject, ASWebAuthenticationPresentationContextProviding {

    var window: UIWindow? = nil

    func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
        return window ?? ASPresentationAnchor()
    }

    func signIn(window: UIWindow?) {
        self.window = window
        let authSession = ASWebAuthenticationSession(url: URL(string: "https://accounts.spotify.com/authorize")!, callbackURLScheme: "myapp-auth") { (url, error) in
            if let error = error {
                print(error.localizedDescription)
            } else if let url = url {
                print(url.absoluteString)
            }
        }
        authSession.presentationContextProvider = self
        authSession.prefersEphemeralWebBrowserSession = true
        authSession.start()
    }
}

struct HostingWindowFinder: UIViewRepresentable {
    var callback: (UIWindow?) -> ()
    func makeUIView(context: Context) -> UIView {
        let view = UIView()
        DispatchQueue.main.async { [weak view] in
            self.callback(view?.window)
        }
        return view
    }

    func updateUIView(_ uiView: UIView, context: Context) {
    }
}

Replies

This earlier thread looks to be the same as my 2nd issue. It's currently unresolved though.