feat: Add presentation window support#1085
feat: Add presentation window support#1085NandanPrabhu wants to merge 4 commits intodevelop/v3.0from
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds support for presenting Web Auth flows from a caller-specified window (UIWindow/NSWindow) to better support multi-window / multi-scene apps across iOS, visionOS, and macOS, and includes test coverage plus demo app wiring.
Changes:
- Extended WebAuth/Auth0WebAuth + ASWebAuthenticationSession provider plumbing to accept an optional presentation window/anchor.
- Updated Safari/WKWebView user agents to present from the top view controller of a provided UIWindow (fallback to key window).
- Added Quick/Nimble specs validating
presentationWindow(...)behavior and provider propagation; enabled multi-scene support in the demo app’s Info.plist.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| Auth0Tests/WebAuthPresentationWindowSpec.swift | Adds specs for presentationWindow(...) and provider window propagation. |
| Auth0/WebAuth.swift | Extends WebAuth protocol with presentationWindow(_:) API (UIWindow/NSWindow). |
| Auth0/Auth0WebAuth.swift | Stores presentation window and passes it to the default AS provider for login/logout. |
| Auth0/ASProvider.swift | Extends AS provider factory + ASUserAgent to carry a presentation window and use it as anchor. |
| Auth0/MobileWebAuth.swift | Uses provided UIWindow as the ASPresentationAnchor on iOS/visionOS. |
| Auth0/DesktopWebAuth.swift | Uses provided NSWindow as the ASPresentationAnchor on macOS. |
| Auth0/SafariProvider.swift | Allows Safari user agent to present from a provided UIWindow. |
| Auth0/WebViewProvider.swift | Allows WKWebView user agent to present from a provided UIWindow. |
| Auth0/UIWindow+TopViewController.swift | Exposes a helper to find the top VC from a specific root VC. |
| App/Info.plist | Enables multiple scenes in the demo app. |
| App/ContentView.swift | Captures current UIWindow (iOS/visionOS) and passes it to login/logout calls. |
| App/ContentViewModel.swift | Adds window-aware login/logout variants and threads the window into WebAuth. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| #if os(iOS) | ||
| import UIKit | ||
| typealias PlatformWindow = UIWindow | ||
| #elseif os(macOS) | ||
| import AppKit | ||
| typealias PlatformWindow = NSWindow | ||
| #endif |
There was a problem hiding this comment.
UIWindow is referenced under #if os(iOS) || os(visionOS) but UIKit is only imported for os(iOS). This will fail to compile the test target on visionOS; update the conditional import to include visionOS (or avoid direct UIKit types in the visionOS blocks).
| #if os(iOS) || os(visionOS) | ||
| import UIKit | ||
| #endif | ||
|
|
There was a problem hiding this comment.
This file conditionally defines APIs using NSWindow for os(macOS), but AppKit is never imported. A macOS build of the demo app will fail to compile; add an #if os(macOS) import for AppKit (and keep UIKit under iOS/visionOS).
| #if os(macOS) | |
| import AppKit | |
| #endif |
App/ContentViewModel.swift
Outdated
| // Use specific window for multi-window iPad support | ||
| #if os(iOS) || os(visionOS) | ||
| if let window = window { | ||
| webAuth = webAuth.presentationWindow(window) | ||
| } | ||
| #endif |
There was a problem hiding this comment.
In the os(macOS) implementation, the presentationWindow parameter is NSWindow? but the code that applies it is wrapped in #if os(iOS) || os(visionOS), so it never runs on macOS and the parameter becomes unused. Apply the window under the macOS branch (and update the comment about “UIWindow / iPad” accordingly).
App/ContentViewModel.swift
Outdated
| // Use specific window for multi-window iPad support | ||
| #if os(iOS) || os(visionOS) | ||
| if let window = window { | ||
| webAuth = webAuth.presentationWindow(window) | ||
| } | ||
| #endif |
There was a problem hiding this comment.
Same issue as webLogin: in the os(macOS) logout implementation, the NSWindow? parameter is only used inside an #if os(iOS) || os(visionOS) block, so it will never be applied on macOS. Use the macOS presentationWindow(_:) API here (and update the doc comment to refer to NSWindow).
| #if os(macOS) | ||
| @State private var currentWindow: NSWindow? | ||
| #else | ||
| @State private var currentWindow: UIWindow? | ||
| #endif |
There was a problem hiding this comment.
NSWindow is referenced under #if os(macOS) but AppKit is not imported anywhere in this file. If the demo app is built for macOS, this will not compile; add an #if os(macOS) import for AppKit.
| Button { | ||
| Task { | ||
| #if !os(macOS) | ||
| await viewModel.webLogin(presentationWindow: currentWindow) | ||
| #endif | ||
| } |
There was a problem hiding this comment.
Both button actions are compiled out on macOS (#if !os(macOS)), so the demo app UI does nothing on macOS even though ContentViewModel provides macOS-specific webLogin/logout overloads. Either call the macOS overloads under #if os(macOS) or remove the macOS branches from the view/model to avoid dead code paths.
| .onAppear { | ||
| // Capture the current window for multi-window iPad support | ||
| #if os(iOS) || os(visionOS) | ||
| currentWindow = getCurrentWindow() | ||
| #endif | ||
| } |
There was a problem hiding this comment.
On macOS, currentWindow is never populated (the .onAppear window capture is iOS/visionOS-only). If macOS support is intended, populate currentWindow from NSApp.keyWindow/NSApplication.shared.keyWindow (or remove currentWindow on macOS if it’s not needed).
|
I have added mandate PR CI checks and 1 mandatory approval for |
App/ContentView.swift
Outdated
| import Auth0 | ||
|
|
||
| #if os(iOS) || os(visionOS) | ||
| import UIKit |
There was a problem hiding this comment.
why do we have optional UIKit import here?
There was a problem hiding this comment.
Do we need to import AppKit for NSWindow in MacOS?
Auth0/Auth0WebAuth.swift
Outdated
| private(set) var provider: WebAuthProvider? | ||
| private(set) var onCloseCallback: (() -> Void)? | ||
| #if os(macOS) | ||
| private(set) var presentationWindow: NSWindow? |
There was a problem hiding this comment.
We should store the weak references for these since the window is owned by the application, not by the Auth0WebAuth instance.
private(set) weak var presentationWindow: NSWindow?
private(set) var presentationWindow: UIWindow?
| let callback: WebAuthProviderCallback | ||
|
|
||
| init(authorizeURL: URL, redirectURL: URL, viewController: UIViewController = UIViewController(), modalPresentationStyle: UIModalPresentationStyle = .fullScreen, callback: @escaping WebAuthProviderCallback) { | ||
| var presentationWindow: UIWindow? |
There was a problem hiding this comment.
we should be using weak reference in SDK
There was a problem hiding this comment.
this should be private(set)
| } | ||
|
|
||
| private static func findTopViewController(from root: UIViewController) -> UIViewController? { | ||
| static func findTopViewController(from root: UIViewController) -> UIViewController? { |
There was a problem hiding this comment.
should we make this private back .
App/ContentViewModel.swift
Outdated
|
|
||
| /// Logout and clear stored credentials | ||
| func logout() async { | ||
| /// - Parameter window: The UIWindow to present the authentication browser. Used for multi-window iPad apps. |
There was a problem hiding this comment.
Do update this comment . This API is being used for MacOS
App/ContentViewModel.swift
Outdated
| #if os(macOS) | ||
| /// Web Authentication using Universal Login (Recommended) | ||
| func webLogin() async { | ||
| /// - Parameter window: The UIWindow to present the authentication browser. Used for multi-window iPad apps. |
There was a problem hiding this comment.
this paramter is being used for MacOS
App/ContentViewModel.swift
Outdated
| var webAuth = Auth0.webAuth() | ||
|
|
||
| // Use specific window for multi-window iPad support | ||
| #if os(iOS) || os(visionOS) |
There was a problem hiding this comment.
#if os(iOS) || os(visionOS) /
if let window = window {
webAuth = webAuth.presentationWindow(window)
}
#endif
This will never execute as the current logout function will be executed for MacOS
App/ContentViewModel.swift
Outdated
|
|
||
|
|
||
| // Use specific window for multi-window iPad support | ||
| #if os(iOS) || os(visionOS) |
There was a problem hiding this comment.
#if os(iOS) || os(visionOS) /
if let window = window {
webAuth = webAuth.presentationWindow(window)
}
#endif
This will never execute as the current login function will be executed for MacOS
| /// - Parameter window: The UIWindow to present the authentication browser. Used for multi-window iPad apps. | ||
| func webLogin(presentationWindow window: NSWindow? = nil) async { | ||
| isLoading = true | ||
| errorMessage = nil |
There was a problem hiding this comment.
Lets update the documentation . We should have Multi Window Support in examples.md file
| #endif | ||
|
|
||
| #if os(macOS) | ||
| init(session: ASWebAuthenticationSession, |
There was a problem hiding this comment.
we should not add have 2 initialiser implementation
Please user the the below design. It improved readability and eliminates the code duplicity
#if os(macOS)
typealias PlatformWindow = NSWindow
#else
typealias PlatformWindow = UIWindow
#endif
class ASUserAgent: NSObject, WebAuthUserAgent {
var presentationWindow: PlatformWindow?
init(session: ASWebAuthenticationSession,
callback: @escaping WebAuthProviderCallback,
presentationWindow: PlatformWindow? = nil) {
// ... single implementation
}
}
| } | ||
|
|
||
| #if os(macOS) | ||
| @available(macOS 10.15, *) |
There was a problem hiding this comment.
#if os(macOS)
typealias PlatformWindow = NSWindow
#else
typealias PlatformWindow = UIWindow
#endif
final class Auth0WebAuth: WebAuth {
// ... properties ...
private(set) weak var presentationWindow: PlatformWindow?
// ... later in the file ...
@available(iOS 13.0, macOS 10.15, visionOS 1.0, *)
func presentationWindow(_ window: PlatformWindow) -> Self {
self.presentationWindow = window
return self
}
}
Preferred design we should use same approach everywhere
- Eliminates duplicate property declarations
- Single method definition instead of two platform-specific versions
- Easier to maintain - changes only need to be made once
| func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { | ||
| return UIApplication.shared()?.windows.last(where: \.isKeyWindow) ?? ASPresentationAnchor() | ||
| // Use custom window if provided | ||
| if let window = presentationWindow { |
There was a problem hiding this comment.
could we use guard here
| #if os(visionOS) | ||
| func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { | ||
| // Use custom window if provided | ||
| if let window = presentationWindow { |
📋 Changes
UIWindow/NSWindowthroughContentView,ContentViewModel,Auth0WebAuth, and all built-in web auth providers, plus enabling multi-scene mode inInfo.plist.WebAuthAPI surface withpresentationWindow(...), updatedASUserAgent, Safari/WKWebView providers, and ASWebAuthentication scaffolding to honor custom anchors on iOS, visionOS, and macOS.offline_access, set credentials managerminTTLto"Username-Password-Authentication"connection, with aligned documentation updates and new Quick/Nimble specs covering default behaviors and presentation windows.🎯 Testing
WebAuthPresentationWindowSpec, extendedAuthenticationSpec, and additionalCredentialsManagerSpeccases to verify defaults and window handling.