SCAFactors

WebAuthn passkey on iOS

This guide provides an overview of how to get started integrating the WebAuthn passkey factor in iOS apps, for secure web-based authentication.

On iOS, ASWebAuthenticationSession enables SwiftUI apps to authenticate users via a web service, presenting a secure and user-friendly popup browser for login flows.

Setup

  1. Import the required frameworks:
  • AuthenticationServices
  • SwiftUI
  1. Define your backend authentication URL and a custom URL scheme for callback handling.

Full code example

1import AuthenticationServices
2import SwiftUI
3
4// MARK: - Configuration Constants
5
6let const_baseUrl = "https://example.com" // Set your backend authentication URL here
7let const_scheme = "mgpsca" // Your custom URL scheme
8let const_returnParam = "&returnUrl=\(const_scheme)://auth" // Return URL for backend to redirect after authentication
9
10struct ContentView: View {
11 @State private var asPresentationContext: PresentationContextProvider? = nil
12
13 /// Starts the web authentication session
14 func startAuthentication() {
15 let strUrl = const_baseUrl + const_returnParam
16 guard let authURL = URL(string: strUrl) else {
17 print("Invalid URL")
18 return
19 }
20 let callbackScheme = const_scheme
21
22 let authSession = ASWebAuthenticationSession(
23 url: authURL,
24 callbackURLScheme: callbackScheme
25 ) { callbackURL, error in
26 // This closure is called when authentication completes or fails
27
28 if let error = error {
29 // Handle authentication error (e.g., user cancelled, network error)
30 print("Authentication failed with error: \(error.localizedDescription)")
31 return
32 }
33
34 guard let callbackURL = callbackURL else {
35 // No callback URL means authentication did not complete
36 print("No callback URL received")
37 return
38 }
39
40 // Handle the callback URL received from the authentication flow
41 handleIncomingURL(callbackURL)
42 }
43
44 // Provide presentation context so the session knows where to present the web browser
45 asPresentationContext = PresentationContextProvider()
46 authSession.presentationContextProvider = asPresentationContext
47
48 // Use ephemeral session to avoid sharing cookies with Safari (incognito-like)(no Pop-up)
49 authSession.prefersEphemeralWebBrowserSession = true
50
51 // Start the authentication session
52 authSession.start()
53 }
54
55 /// Handles the URL returned from the authentication flow
56 func handleIncomingURL(_ url: URL) {
57 print("Incoming URL: \(url)")
58
59 // Ensure the URL uses the expected custom scheme
60 guard let scheme = url.scheme, scheme == const_scheme else {
61 print("Invalid URL scheme")
62 return
63 }
64
65 // Parse the URL into components to extract query parameter
66 guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
67 print("Invalid URL components")
68 return
69 }
70
71 guard let queryItems = components.queryItems else {
72 print("No query items found")
73 return
74 }
75
76 // Extract authentication status from query parameter
77 let controlStatusValue = queryItems.first(where: { $0.name == "controlStatus" })?.value ?? ""
78
79 // Check if authentication was successful
80 if controlStatusValue == "VALIDATED" {
81 print("SCA succeeded")
82 } else {
83 // Handle authentication failure or unexpected status
84 print("SCA failed : \(queryItems)")
85 }
86 }
87}
88
89// MARK: - Presentation Context Provider for ASWebAuthenticationSession
90
91/// Provides the presentation anchor (window) for ASWebAuthenticationSession
92class PresentationContextProvider: NSObject, ASWebAuthenticationPresentationContextProviding {
93 func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
94 if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
95 let window = windowScene.windows.first {
96 return window
97 }
98 return ASPresentationAnchor()
99 }
100}

Notes

  • Replace const_baseUrl with your backend authentication URL.
  • The session uses prefersEphemeralWebBrowserSession = true to avoid sharing cookies or data with Safari and popups.

Troubleshooting

  • Ensure the callback URL scheme matches your app’s registered scheme.