Skip to content

Commit 2515089

Browse files
Add support for custom Web Auth providers [SDK-3338] (#699)
* Add custom Web Auth providers implementation [SDK-3333] (#688) * Implement custom Web Auth user agents * Use a static factory * Rename Safari provider methods * Move custom description into user agents * Remove optionals * Add comment to SafariUserAgent * Simplify implementation * Return the closures directly * Move Safari provider inside the library * Handle other ASWebAuthenticationSessionError cases * Move common logic to an extension * Fix tests * Remove whitespace * Simplify implementation of custom Web Auth providers (#691) * Add unit tests [SDK-3335] (#692) * Add unit tests * Remove unnecessary import * Fix matcher name * Use ASProvider in WebAuth test * Add platform guards * Update Auth0Tests/SafariProviderSpec.swift Co-authored-by: Adam Mcgrath <[email protected]> * Add additional unit tests (#694) * Simplify the implementation some more (#695) * Add API documentation (#697) * Add API documentation * Add link to example implementation * Update Auth0/WebAuthUserAgent.swift * Update Auth0/WebAuthUserAgent.swift * Update FAQ and README (#698) Co-authored-by: Adam Mcgrath <[email protected]>
1 parent c8196ea commit 2515089

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1473
-588
lines changed

App/AppDelegate.swift

+4-1
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
1010
return true
1111
}
1212

13-
}
13+
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
14+
return WebAuthentication.resume(with: url)
15+
}
1416

17+
}

App/ViewController.swift

+9-7
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ class ViewController: UIViewController {
1010

1111
self.onAuth = {
1212
switch $0 {
13-
case .failure(let cause):
13+
case .failure(let error):
1414
DispatchQueue.main.async {
15-
self.alert(title: "Error", message: "\(cause)")
15+
self.alert(title: "Error", message: "\(error)")
1616
}
1717
case .success(let credentials):
1818
DispatchQueue.main.async {
@@ -25,13 +25,15 @@ class ViewController: UIViewController {
2525
}
2626

2727
@IBAction func login(_ sender: Any) {
28-
Auth0.webAuth()
28+
Auth0
29+
.webAuth()
2930
.logging(enabled: true)
3031
.start(onAuth)
3132
}
32-
33+
3334
@IBAction func logout(_ sender: Any) {
34-
Auth0.webAuth()
35+
Auth0
36+
.webAuth()
3537
.logging(enabled: true)
3638
.clearSession(federated: false) { result in
3739
switch result {
@@ -44,12 +46,12 @@ class ViewController: UIViewController {
4446
}
4547

4648
extension UIViewController {
47-
49+
4850
func alert(title: String, message: String) {
4951
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
5052
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
5153

5254
present(alert, animated: true)
5355
}
54-
56+
5557
}

Auth0.podspec

+7-6
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
11
web_auth_files = [
22
'Auth0/Array+Encode.swift',
3-
'Auth0/ASCallbackTransaction.swift',
4-
'Auth0/ASTransaction.swift',
5-
'Auth0/AuthSession.swift',
3+
'Auth0/ASProvider.swift',
64
'Auth0/AuthTransaction.swift',
75
'Auth0/Auth0WebAuth.swift',
8-
'Auth0/BaseCallbackTransaction.swift',
9-
'Auth0/BaseTransaction.swift',
106
'Auth0/BioAuthentication.swift',
117
'Auth0/ChallengeGenerator.swift',
128
'Auth0/ClaimValidators.swift',
9+
'Auth0/ClearSessionTransaction.swift',
1310
'Auth0/IDTokenSignatureValidator.swift',
1411
'Auth0/IDTokenValidator.swift',
1512
'Auth0/IDTokenValidatorContext.swift',
1613
'Auth0/JWK+RSA.swift',
1714
'Auth0/JWT+Header.swift',
1815
'Auth0/JWTAlgorithm.swift',
16+
'Auth0/LoginTransaction.swift',
1917
'Auth0/NSURLComponents+OAuth2.swift',
2018
'Auth0/OAuth2Grant.swift',
19+
'Auth0/SafariProvider.swift',
2120
'Auth0/TransactionStore.swift',
2221
'Auth0/WebAuth.swift',
23-
'Auth0/WebAuthError.swift'
22+
'Auth0/WebAuthentication.swift',
23+
'Auth0/WebAuthError.swift',
24+
'Auth0/WebAuthUserAgent.swift'
2425
]
2526

2627
ios_files = ['Auth0/MobileWebAuth.swift']

Auth0.xcodeproj/project.pbxproj

+99-39
Large diffs are not rendered by default.

Auth0/ASCallbackTransaction.swift

-32
This file was deleted.

Auth0/ASProvider.swift

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#if WEB_AUTH_PLATFORM
2+
import AuthenticationServices
3+
4+
extension WebAuthentication {
5+
6+
static func asProvider(urlScheme: String, ephemeralSession: Bool = false) -> WebAuthProvider {
7+
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+
callback(.failure(WebAuthError(code: .userCancelled)))
12+
} else if let error = $1 {
13+
callback(.failure(WebAuthError(code: .other, cause: error)))
14+
} else {
15+
callback(.failure(WebAuthError(code: .unknown("ASWebAuthenticationSession failed"))))
16+
}
17+
18+
return TransactionStore.shared.clear()
19+
}
20+
21+
_ = TransactionStore.shared.resume(callbackURL)
22+
}
23+
24+
if #available(iOS 13.0, *) {
25+
session.prefersEphemeralWebBrowserSession = ephemeralSession
26+
}
27+
28+
return ASUserAgent(session: session, callback: callback)
29+
}
30+
}
31+
32+
}
33+
34+
class ASUserAgent: NSObject, WebAuthUserAgent {
35+
36+
let session: ASWebAuthenticationSession
37+
let callback: (WebAuthResult<Void>) -> Void
38+
39+
init(session: ASWebAuthenticationSession, callback: @escaping (WebAuthResult<Void>) -> Void) {
40+
self.session = session
41+
self.callback = callback
42+
super.init()
43+
44+
if #available(iOS 13.0, *) {
45+
session.presentationContextProvider = self
46+
}
47+
}
48+
49+
func start() {
50+
_ = self.session.start()
51+
}
52+
53+
func finish(with result: WebAuthResult<Void>) {
54+
callback(result)
55+
}
56+
57+
public override var description: String {
58+
return String(describing: ASWebAuthenticationSession.self)
59+
}
60+
61+
}
62+
#endif

Auth0/ASTransaction.swift

-48
This file was deleted.

Auth0/Auth0.swift

+3-8
Original file line numberDiff line numberDiff line change
@@ -197,18 +197,13 @@ public func webAuth(clientId: String, domain: String, session: URLSession = .sha
197197
#endif
198198

199199
func plistValues(bundle: Bundle) -> (clientId: String, domain: String)? {
200-
guard
201-
let path = bundle.path(forResource: "Auth0", ofType: "plist"),
202-
let values = NSDictionary(contentsOfFile: path) as? [String: Any]
203-
else {
200+
guard let path = bundle.path(forResource: "Auth0", ofType: "plist"),
201+
let values = NSDictionary(contentsOfFile: path) as? [String: Any] else {
204202
print("Missing Auth0.plist file with 'ClientId' and 'Domain' entries in main bundle!")
205203
return nil
206204
}
207205

208-
guard
209-
let clientId = values["ClientId"] as? String,
210-
let domain = values["Domain"] as? String
211-
else {
206+
guard let clientId = values["ClientId"] as? String, let domain = values["Domain"] as? String else {
212207
print("Auth0.plist file at \(path) is missing 'ClientId' and/or 'Domain' entries!")
213208
print("File currently has the following entries: \(values)")
214209
return nil

Auth0/Auth0WebAuth.swift

+44-20
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ final class Auth0WebAuth: WebAuth {
2929
private(set) var maxAge: Int?
3030
private(set) var organization: String?
3131
private(set) var invitationURL: URL?
32+
private(set) var provider: WebAuthProvider?
33+
34+
var state: String {
35+
return self.parameters["state"] ?? self.generateDefaultState()
36+
}
3237

3338
lazy var redirectURL: URL? = {
3439
guard let bundleIdentifier = Bundle.main.bundleIdentifier else { return nil }
@@ -123,20 +128,28 @@ final class Auth0WebAuth: WebAuth {
123128
return self
124129
}
125130

131+
func provider(_ provider: @escaping WebAuthProvider) -> Self {
132+
self.provider = provider
133+
return self
134+
}
135+
126136
func start(_ callback: @escaping (WebAuthResult<Credentials>) -> Void) {
127-
guard let redirectURL = self.redirectURL else {
137+
guard let redirectURL = self.redirectURL, let urlScheme = redirectURL.scheme else {
128138
return callback(.failure(WebAuthError(code: .noBundleIdentifier)))
129139
}
140+
130141
let handler = self.handler(redirectURL)
131-
let state = self.parameters["state"] ?? generateDefaultState()
142+
let state = self.state
132143
var organization: String? = self.organization
133144
var invitation: String?
145+
134146
if let invitationURL = self.invitationURL {
135147
guard let queryItems = URLComponents(url: invitationURL, resolvingAgainstBaseURL: false)?.queryItems,
136148
let organizationId = queryItems.first(where: { $0.name == "organization" })?.value,
137149
let invitationId = queryItems.first(where: { $0.name == "invitation" })?.value else {
138150
return callback(.failure(WebAuthError(code: .invalidInvitationURL(invitationURL.absoluteString))))
139151
}
152+
140153
organization = organizationId
141154
invitation = invitationId
142155
}
@@ -146,36 +159,45 @@ final class Auth0WebAuth: WebAuth {
146159
state: state,
147160
organization: organization,
148161
invitation: invitation)
149-
let session = ASTransaction(authorizeURL: authorizeURL,
150-
redirectURL: redirectURL,
151-
state: state,
152-
handler: handler,
153-
logger: self.logger,
154-
ephemeralSession: self.ephemeralSession,
155-
callback: callback)
156-
logger?.trace(url: authorizeURL, source: String(describing: session.self))
157-
self.storage.store(session)
162+
let provider = self.provider ?? WebAuthentication.asProvider(urlScheme: urlScheme,
163+
ephemeralSession: ephemeralSession)
164+
let userAgent = provider(authorizeURL) { result in
165+
if case let .failure(error) = result {
166+
callback(.failure(error))
167+
}
168+
}
169+
let transaction = LoginTransaction(redirectURL: redirectURL,
170+
state: state,
171+
userAgent: userAgent,
172+
handler: handler,
173+
logger: self.logger,
174+
callback: callback)
175+
userAgent.start()
176+
self.storage.store(transaction)
177+
logger?.trace(url: authorizeURL, source: String(describing: userAgent.self))
158178
}
159179

160180
func clearSession(federated: Bool, callback: @escaping (WebAuthResult<Void>) -> Void) {
161181
let endpoint = federated ?
162182
URL(string: "v2/logout?federated", relativeTo: self.url)! :
163183
URL(string: "v2/logout", relativeTo: self.url)!
164-
165184
let returnTo = URLQueryItem(name: "returnTo", value: self.redirectURL?.absoluteString)
166185
let clientId = URLQueryItem(name: "client_id", value: self.clientId)
167186
var components = URLComponents(url: endpoint, resolvingAgainstBaseURL: true)
168187
let queryItems = components?.queryItems ?? []
169188
components?.queryItems = queryItems + [returnTo, clientId]
170189

171-
guard let logoutURL = components?.url, let redirectURL = self.redirectURL else {
190+
guard let logoutURL = components?.url,
191+
let redirectURL = self.redirectURL,
192+
let urlScheme = redirectURL.scheme else {
172193
return callback(.failure(WebAuthError(code: .noBundleIdentifier)))
173194
}
174195

175-
let session = ASCallbackTransaction(url: logoutURL,
176-
schemeURL: redirectURL,
177-
callback: callback)
178-
self.storage.store(session)
196+
let provider = self.provider ?? WebAuthentication.asProvider(urlScheme: urlScheme)
197+
let userAgent = provider(logoutURL, callback)
198+
let transaction = ClearSessionTransaction(userAgent: userAgent)
199+
userAgent.start()
200+
self.storage.store(transaction)
179201
}
180202

181203
func buildAuthorizeURL(withRedirectURL redirectURL: URL,
@@ -210,16 +232,18 @@ final class Auth0WebAuth: WebAuth {
210232
return components.url!
211233
}
212234

213-
func generateDefaultState() -> String? {
235+
func generateDefaultState() -> String {
214236
let data = Data(count: 32)
215237
var tempData = data
216238

217239
let result = tempData.withUnsafeMutableBytes {
218240
SecRandomCopyBytes(kSecRandomDefault, data.count, $0.baseAddress!)
219241
}
220242

221-
guard result == 0 else { return nil }
222-
return tempData.a0_encodeBase64URLSafe()
243+
guard result == 0, let state = tempData.a0_encodeBase64URLSafe()
244+
else { return UUID().uuidString.replacingOccurrences(of: "-", with: "") }
245+
246+
return state
223247
}
224248

225249
private func handler(_ redirectURL: URL) -> OAuth2Grant {

Auth0/AuthSession.swift

-8
This file was deleted.

0 commit comments

Comments
 (0)