Skip to content

Commit f051007

Browse files
authored
Add support for HTTPS callbacks [SDK-4749] (#832)
1 parent eb9eea5 commit f051007

7 files changed

+191
-82
lines changed

Auth0/ASProvider.swift

+44-14
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,68 @@
11
#if WEB_AUTH_PLATFORM
22
import AuthenticationServices
33

4+
typealias ASHandler = ASWebAuthenticationSession.CompletionHandler
5+
46
extension WebAuthentication {
57

6-
static func asProvider(urlScheme: String, ephemeralSession: Bool = false) -> WebAuthProvider {
8+
static func asProvider(redirectURL: URL, ephemeralSession: Bool = false) -> WebAuthProvider {
79
return { url, callback in
8-
let session = ASWebAuthenticationSession(url: url, callbackURLScheme: urlScheme) {
9-
guard let callbackURL = $0, $1 == nil else {
10-
if let error = $1, case ASWebAuthenticationSessionError.canceledLogin = error {
11-
return callback(.failure(WebAuthError(code: .userCancelled)))
12-
} else if let error = $1 {
13-
return callback(.failure(WebAuthError(code: .other, cause: error)))
14-
}
15-
16-
return callback(.failure(WebAuthError(code: .unknown("ASWebAuthenticationSession failed"))))
17-
}
10+
let session: ASWebAuthenticationSession
1811

19-
_ = TransactionStore.shared.resume(callbackURL)
12+
#if compiler(>=5.10)
13+
if #available(iOS 17.4, macOS 14.4, *) {
14+
if redirectURL.scheme == "https" {
15+
session = ASWebAuthenticationSession(url: url,
16+
callback: .https(host: redirectURL.host!,
17+
path: redirectURL.path),
18+
completionHandler: completionHandler(callback))
19+
} else {
20+
session = ASWebAuthenticationSession(url: url,
21+
callback: .customScheme(redirectURL.scheme!),
22+
completionHandler: completionHandler(callback))
23+
}
24+
} else {
25+
session = ASWebAuthenticationSession(url: url,
26+
callbackURLScheme: redirectURL.scheme,
27+
completionHandler: completionHandler(callback))
2028
}
29+
#else
30+
session = ASWebAuthenticationSession(url: url,
31+
callbackURLScheme: redirectURL.scheme,
32+
completionHandler: completionHandler(callback))
33+
#endif
2134

2235
session.prefersEphemeralWebBrowserSession = ephemeralSession
2336

2437
return ASUserAgent(session: session, callback: callback)
2538
}
2639
}
2740

41+
static let completionHandler: (_ callback: @escaping WebAuthProviderCallback) -> ASHandler = { callback in
42+
return {
43+
guard let callbackURL = $0, $1 == nil else {
44+
if let error = $1 as? NSError,
45+
error.userInfo.isEmpty,
46+
case ASWebAuthenticationSessionError.canceledLogin = error {
47+
return callback(.failure(WebAuthError(code: .userCancelled)))
48+
} else if let error = $1 {
49+
return callback(.failure(WebAuthError(code: .other, cause: error)))
50+
}
51+
52+
return callback(.failure(WebAuthError(code: .unknown("ASWebAuthenticationSession failed"))))
53+
}
54+
55+
_ = TransactionStore.shared.resume(callbackURL)
56+
}
57+
}
2858
}
2959

3060
class ASUserAgent: NSObject, WebAuthUserAgent {
3161

3262
let session: ASWebAuthenticationSession
33-
let callback: (WebAuthResult<Void>) -> Void
63+
let callback: WebAuthProviderCallback
3464

35-
init(session: ASWebAuthenticationSession, callback: @escaping (WebAuthResult<Void>) -> Void) {
65+
init(session: ASWebAuthenticationSession, callback: @escaping WebAuthProviderCallback) {
3666
self.session = session
3767
self.callback = callback
3868
super.init()

Auth0/Auth0WebAuth.swift

+25-10
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ final class Auth0WebAuth: WebAuth {
2020
private let responseType = "code"
2121

2222
private(set) var parameters: [String: String] = [:]
23+
private(set) var https = false
2324
private(set) var ephemeralSession = false
2425
private(set) var issuer: String
2526
private(set) var leeway: Int = 60 * 1000 // Default leeway is 60 seconds
@@ -35,15 +36,26 @@ final class Auth0WebAuth: WebAuth {
3536
}
3637

3738
lazy var redirectURL: URL? = {
38-
guard let bundleIdentifier = Bundle.main.bundleIdentifier,
39-
let domain = self.url.host,
40-
let baseURL = URL(string: "\(bundleIdentifier)://\(domain)") else { return nil }
39+
guard let bundleID = Bundle.main.bundleIdentifier, let domain = self.url.host else { return nil }
40+
let scheme: String
41+
42+
#if compiler(>=5.10)
43+
if #available(iOS 17.4, macOS 14.4, *) {
44+
scheme = https ? "https" : bundleID
45+
} else {
46+
scheme = bundleID
47+
}
48+
#else
49+
scheme = bundleID
50+
#endif
4151

52+
guard let baseURL = URL(string: "\(scheme)://\(domain)") else { return nil }
4253
var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: true)
54+
4355
return components?.url?
4456
.appendingPathComponent(self.url.path)
4557
.appendingPathComponent(self.platform)
46-
.appendingPathComponent(bundleIdentifier)
58+
.appendingPathComponent(bundleID)
4759
.appendingPathComponent("callback")
4860
}()
4961

@@ -115,6 +127,11 @@ final class Auth0WebAuth: WebAuth {
115127
return self
116128
}
117129

130+
func useHTTPS() -> Self {
131+
self.https = true
132+
return self
133+
}
134+
118135
func useEphemeralSession() -> Self {
119136
self.ephemeralSession = true
120137
return self
@@ -141,7 +158,7 @@ final class Auth0WebAuth: WebAuth {
141158
}
142159

143160
func start(_ callback: @escaping (WebAuthResult<Credentials>) -> Void) {
144-
guard let redirectURL = self.redirectURL, let urlScheme = redirectURL.scheme else {
161+
guard let redirectURL = self.redirectURL else {
145162
return callback(.failure(WebAuthError(code: .noBundleIdentifier)))
146163
}
147164

@@ -166,7 +183,7 @@ final class Auth0WebAuth: WebAuth {
166183
state: state,
167184
organization: organization,
168185
invitation: invitation)
169-
let provider = self.provider ?? WebAuthentication.asProvider(urlScheme: urlScheme,
186+
let provider = self.provider ?? WebAuthentication.asProvider(redirectURL: redirectURL,
170187
ephemeralSession: ephemeralSession)
171188
let userAgent = provider(authorizeURL) { [storage, onCloseCallback] result in
172189
storage.clear()
@@ -199,13 +216,11 @@ final class Auth0WebAuth: WebAuth {
199216
let queryItems = components?.queryItems ?? []
200217
components?.queryItems = queryItems + [returnTo, clientId]
201218

202-
guard let logoutURL = components?.url,
203-
let redirectURL = self.redirectURL,
204-
let urlScheme = redirectURL.scheme else {
219+
guard let logoutURL = components?.url, let redirectURL = self.redirectURL else {
205220
return callback(.failure(WebAuthError(code: .noBundleIdentifier)))
206221
}
207222

208-
let provider = self.provider ?? WebAuthentication.asProvider(urlScheme: urlScheme)
223+
let provider = self.provider ?? WebAuthentication.asProvider(redirectURL: redirectURL)
209224
let userAgent = provider(logoutURL) { [storage] result in
210225
storage.clear()
211226
callback(result)

Auth0/SafariProvider.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,9 @@ extension SFSafariViewController {
8181
class SafariUserAgent: NSObject, WebAuthUserAgent {
8282

8383
let controller: SFSafariViewController
84-
let callback: ((WebAuthResult<Void>) -> Void)
84+
let callback: WebAuthProviderCallback
8585

86-
init(controller: SFSafariViewController, callback: @escaping (WebAuthResult<Void>) -> Void) {
86+
init(controller: SFSafariViewController, callback: @escaping WebAuthProviderCallback) {
8787
self.controller = controller
8888
self.callback = callback
8989
super.init()

Auth0/WebAuth.swift

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
1+
// swiftlint:disable file_length
2+
13
#if WEB_AUTH_PLATFORM
24
import Foundation
35
import Combine
46

7+
/// Callback invoked by the ``WebAuthUserAgent`` when the web-based operation concludes.
8+
public typealias WebAuthProviderCallback = (WebAuthResult<Void>) -> Void
9+
510
/// Thunk that returns a function that creates and returns a ``WebAuthUserAgent`` to perform a web-based operation.
611
/// The ``WebAuthUserAgent`` opens the URL in an external user agent and then invokes the callback when done.
712
///
813
/// ## See Also
914
///
1015
/// - [Example](https://github.com/auth0/Auth0.swift/blob/master/Auth0/SafariProvider.swift)
11-
public typealias WebAuthProvider = (_ url: URL, _ callback: @escaping (WebAuthResult<Void>) -> Void) -> WebAuthUserAgent
16+
public typealias WebAuthProvider = (_ url: URL, _ callback: @escaping WebAuthProviderCallback) -> WebAuthUserAgent
1217

1318
/// Web-based authentication using Auth0.
1419
///
@@ -127,6 +132,17 @@ public protocol WebAuth: Trackable, Loggable {
127132
/// - Returns: The same `WebAuth` instance to allow method chaining.
128133
func maxAge(_ maxAge: Int) -> Self
129134

135+
/// Use `https` as the scheme for the redirect URL on iOS 17.4+ and macOS 14.4+. On older versions of iOS and
136+
/// macOS, the bundle identifier of the app will be used as a custom scheme.
137+
///
138+
/// - Returns: The same `WebAuth` instance to allow method chaining.
139+
/// - Requires: An Associated Domain configured with the `webcredentials` service type. For example,
140+
/// `webcredentials:example.com`. If you're using a custom domain on your Auth0 tenant, use this domain as the
141+
/// Associated Domain. Otherwise, use the domain of your Auth0 tenant.
142+
/// - Note: Don't use this method along with ``provider(_:)``. Use either one or the other, because this
143+
/// method will only work with the default `ASWebAuthenticationSession` implementation.
144+
func useHTTPS() -> Self
145+
130146
/// Use a private browser session to avoid storing the session cookie in the shared cookie jar.
131147
/// Using this method will disable single sign-on (SSO).
132148
///

Auth0Tests/ASProviderSpec.swift

+11-7
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import Nimble
55

66
@testable import Auth0
77

8-
private let Url = URL(string: "https://auth0.com")!
8+
private let AuthorizeURL = URL(string: "https://auth0.com")!
9+
private let HTTPSRedirectURL = URL(string: "https://auth0.com/callback")!
10+
private let CustomSchemeRedirectURL = URL(string: "com.auth0.example://samples.auth0.com/callback")!
911
private let Timeout: NimbleTimeInterval = .seconds(2)
1012

1113
class ASProviderSpec: QuickSpec {
@@ -16,7 +18,7 @@ class ASProviderSpec: QuickSpec {
1618
var userAgent: ASUserAgent!
1719

1820
beforeEach {
19-
session = ASWebAuthenticationSession(url: Url, callbackURLScheme: nil, completionHandler: { _, _ in })
21+
session = ASWebAuthenticationSession(url: AuthorizeURL, callbackURLScheme: nil, completionHandler: { _, _ in })
2022
userAgent = ASUserAgent(session: session, callback: { _ in })
2123
}
2224

@@ -27,20 +29,22 @@ class ASProviderSpec: QuickSpec {
2729
describe("WebAuthentication extension") {
2830

2931
it("should create a web authentication session provider") {
30-
let provider = WebAuthentication.asProvider(urlScheme: Url.scheme!)
31-
expect(provider(Url, {_ in })).to(beAKindOf(ASUserAgent.self))
32+
let provider = WebAuthentication.asProvider(redirectURL: HTTPSRedirectURL)
33+
expect(provider(AuthorizeURL, {_ in })).to(beAKindOf(ASUserAgent.self))
3234
}
3335

3436
it("should not use an ephemeral session by default") {
35-
userAgent = WebAuthentication.asProvider(urlScheme: Url.scheme!)(Url, { _ in }) as? ASUserAgent
37+
let provider = WebAuthentication.asProvider(redirectURL: CustomSchemeRedirectURL)
38+
userAgent = provider(AuthorizeURL, { _ in }) as? ASUserAgent
3639
expect(userAgent.session.prefersEphemeralWebBrowserSession) == false
3740
}
3841

3942
it("should use an ephemeral session") {
40-
userAgent = WebAuthentication.asProvider(urlScheme: Url.scheme!,
41-
ephemeralSession: true)(Url, { _ in }) as? ASUserAgent
43+
let provider = WebAuthentication.asProvider(redirectURL: CustomSchemeRedirectURL, ephemeralSession: true)
44+
userAgent = provider(AuthorizeURL, { _ in }) as? ASUserAgent
4245
expect(userAgent.session.prefersEphemeralWebBrowserSession) == true
4346
}
47+
4448
}
4549

4650
describe("user agent") {

0 commit comments

Comments
 (0)