From 43c020074ad9f7f901942a59f68b7a1fb062e2b0 Mon Sep 17 00:00:00 2001 From: Martin Walsh Date: Thu, 28 Dec 2017 14:07:52 +0000 Subject: [PATCH] Updated Credentials Manager (#180) Deprecated enableTouchAuth method -> enableBiometricAuth for clarity Expand README Docs Update Circle CI Xcode Version SwiftLint Fixes --- Auth0.xcodeproj/project.pbxproj | 16 +++++------ Auth0/Auth0Authentication.swift | 2 +- Auth0/AuthenticationError.swift | 2 +- ...tication.swift => BioAuthentication.swift} | 6 ++-- Auth0/CredentialsManager.swift | 28 +++++++++++++------ Auth0/Identity.swift | 2 +- Auth0/Logger.swift | 2 +- Auth0/ManagementError.swift | 2 +- Auth0/OAuth2Grant.swift | 12 ++++---- Auth0/SafariAuthenticationCallback.swift | 2 +- Auth0/WebAuthError.swift | 2 +- Auth0/_ObjectiveManagementAPI.swift | 2 +- ...Spec.swift => BioAuthenticationSpec.swift} | 22 +++++++-------- Auth0Tests/CredentialsManagerSpec.swift | 10 ++++--- README.md | 14 +++++++++- circle.yml | 2 +- 16 files changed, 76 insertions(+), 50 deletions(-) rename Auth0/{TouchAuthentication.swift => BioAuthentication.swift} (95%) rename Auth0Tests/{TouchAuthenticationSpec.swift => BioAuthenticationSpec.swift} (82%) diff --git a/Auth0.xcodeproj/project.pbxproj b/Auth0.xcodeproj/project.pbxproj index 88afa69d..908f870b 100644 --- a/Auth0.xcodeproj/project.pbxproj +++ b/Auth0.xcodeproj/project.pbxproj @@ -23,8 +23,8 @@ 5B5E93F91EC45C22002A37F9 /* CredentialsManagerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B5E93F81EC45C22002A37F9 /* CredentialsManagerError.swift */; }; 5B6269E71E3F702000305093 /* NativeAuthSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6269E51E3F700000305093 /* NativeAuthSpec.swift */; }; 5B6269EA1E3F9E5200305093 /* AuthProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6269E91E3F9E5200305093 /* AuthProvider.swift */; }; - 5B9262C01ECF0CA800F4F6D3 /* TouchAuthentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B9262BF1ECF0CA800F4F6D3 /* TouchAuthentication.swift */; }; - 5B9262C31ECF0CC200F4F6D3 /* TouchAuthenticationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B9262C11ECF0CBA00F4F6D3 /* TouchAuthenticationSpec.swift */; }; + 5B9262C01ECF0CA800F4F6D3 /* BioAuthentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B9262BF1ECF0CA800F4F6D3 /* BioAuthentication.swift */; }; + 5B9262C31ECF0CC200F4F6D3 /* BioAuthenticationSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B9262C11ECF0CBA00F4F6D3 /* BioAuthenticationSpec.swift */; }; 5BD4A9CE1DEC6EFA00D6D7AE /* ResponseType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BD4A9CD1DEC6EFA00D6D7AE /* ResponseType.swift */; }; 5BE65DCA1F7270DE00CADD3B /* Auth0.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5F06DD781CC448B10011842B /* Auth0.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 5BEDE1571EC1FBBE0007300D /* SilentSafariViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BEDE1561EC1FBBE0007300D /* SilentSafariViewController.swift */; }; @@ -328,8 +328,8 @@ 5B5E93F81EC45C22002A37F9 /* CredentialsManagerError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CredentialsManagerError.swift; sourceTree = ""; }; 5B6269E51E3F700000305093 /* NativeAuthSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NativeAuthSpec.swift; path = Auth0Tests/NativeAuthSpec.swift; sourceTree = SOURCE_ROOT; }; 5B6269E91E3F9E5200305093 /* AuthProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AuthProvider.swift; path = Auth0/AuthProvider.swift; sourceTree = SOURCE_ROOT; }; - 5B9262BF1ECF0CA800F4F6D3 /* TouchAuthentication.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TouchAuthentication.swift; sourceTree = ""; }; - 5B9262C11ECF0CBA00F4F6D3 /* TouchAuthenticationSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TouchAuthenticationSpec.swift; path = Auth0Tests/TouchAuthenticationSpec.swift; sourceTree = SOURCE_ROOT; }; + 5B9262BF1ECF0CA800F4F6D3 /* BioAuthentication.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BioAuthentication.swift; sourceTree = ""; }; + 5B9262C11ECF0CBA00F4F6D3 /* BioAuthenticationSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BioAuthenticationSpec.swift; path = Auth0Tests/BioAuthenticationSpec.swift; sourceTree = SOURCE_ROOT; }; 5B9A54411E49E3AE004B5454 /* Auth0.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Auth0.plist; sourceTree = SOURCE_ROOT; }; 5BD4A9CD1DEC6EFA00D6D7AE /* ResponseType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ResponseType.swift; path = Auth0/ResponseType.swift; sourceTree = SOURCE_ROOT; }; 5BEDE1561EC1FBBE0007300D /* SilentSafariViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SilentSafariViewController.swift; path = Auth0/SilentSafariViewController.swift; sourceTree = SOURCE_ROOT; }; @@ -535,7 +535,7 @@ isa = PBXGroup; children = ( 5B1748731EF2D3A40060E653 /* Date.swift */, - 5B9262BF1ECF0CA800F4F6D3 /* TouchAuthentication.swift */, + 5B9262BF1ECF0CA800F4F6D3 /* BioAuthentication.swift */, 5BEDE1891EC21B040007300D /* CredentialsManager.swift */, 5B5E93F81EC45C22002A37F9 /* CredentialsManagerError.swift */, ); @@ -747,7 +747,7 @@ 5FBBF0331CC95FA40024D2AF /* Utils */ = { isa = PBXGroup; children = ( - 5B9262C11ECF0CBA00F4F6D3 /* TouchAuthenticationSpec.swift */, + 5B9262C11ECF0CBA00F4F6D3 /* BioAuthenticationSpec.swift */, 5BEDE1931EC3331A0007300D /* CredentialsManagerSpec.swift */, 5FBBF0371CC964BC0024D2AF /* Matchers.swift */, 5FBBF03A1CC96AA70024D2AF /* Responses.swift */, @@ -1353,7 +1353,7 @@ 5FDE87651D8A424700EA27DC /* Profile.swift in Sources */, 5FE2F8B21CCEAED8003628F4 /* Requestable.swift in Sources */, 5F7504F51D8C3F2900E3BA1C /* NSError+Helper.swift in Sources */, - 5B9262C01ECF0CA800F4F6D3 /* TouchAuthentication.swift in Sources */, + 5B9262C01ECF0CA800F4F6D3 /* BioAuthentication.swift in Sources */, 5FADB60C1CED7E0800D4BB50 /* UserPatchAttributes.swift in Sources */, 5FCAB1791D09124D00331C84 /* NSURL+Auth0.swift in Sources */, 5F74CB401CEFD5E600226823 /* JSONObjectPayload.swift in Sources */, @@ -1422,7 +1422,7 @@ 5FCAB16A1D07AC3500331C84 /* ChallengeGeneratorSpec.swift in Sources */, 5FADB60F1CED7E5200D4BB50 /* UserPatchAttributesSpec.swift in Sources */, 5FA250541D4A85A200C544FA /* WebAuthErrorSpec.swift in Sources */, - 5B9262C31ECF0CC200F4F6D3 /* TouchAuthenticationSpec.swift in Sources */, + 5B9262C31ECF0CC200F4F6D3 /* BioAuthenticationSpec.swift in Sources */, 5FBBF0381CC964BC0024D2AF /* Matchers.swift in Sources */, 5F93BC0B1CC6B0DE0031519F /* Auth0Spec.swift in Sources */, 5FE2F8A61CCA9C17003628F4 /* CredentialsSpec.swift in Sources */, diff --git a/Auth0/Auth0Authentication.swift b/Auth0/Auth0Authentication.swift index 2dc8ff50..ff15d7c3 100644 --- a/Auth0/Auth0Authentication.swift +++ b/Auth0/Auth0Authentication.swift @@ -190,7 +190,7 @@ struct Auth0Authentication: Authentication { return Request(session: session, url: oauthToken, method: "POST", handle: noBody, payload: payload, logger: self.logger, telemetry: self.telemetry) } - func delegation(withParameters parameters: [String : Any]) -> Request<[String : Any], AuthenticationError> { + func delegation(withParameters parameters: [String: Any]) -> Request<[String: Any], AuthenticationError> { var payload: [String: Any] = [ "client_id": self.clientId, "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer" diff --git a/Auth0/AuthenticationError.swift b/Auth0/AuthenticationError.swift index 215949e7..181cabb7 100644 --- a/Auth0/AuthenticationError.swift +++ b/Auth0/AuthenticationError.swift @@ -149,7 +149,7 @@ extension AuthenticationError: CustomNSError { public static let infoKey = "com.auth0.authentication.error.info" public static var errorDomain: String { return "com.auth0.authentication" } public var errorCode: Int { return 1 } - public var errorUserInfo: [String : Any] { + public var errorUserInfo: [String: Any] { return [ NSLocalizedDescriptionKey: self.description, AuthenticationError.infoKey: self diff --git a/Auth0/TouchAuthentication.swift b/Auth0/BioAuthentication.swift similarity index 95% rename from Auth0/TouchAuthentication.swift rename to Auth0/BioAuthentication.swift index f13c6cec..594dc08e 100644 --- a/Auth0/TouchAuthentication.swift +++ b/Auth0/BioAuthentication.swift @@ -1,4 +1,4 @@ -// TouchAuthentication.swift +// BioAuthentication.swift // // Copyright (c) 2017 Auth0 (http://auth0.com) // @@ -23,7 +23,7 @@ import Foundation import LocalAuthentication -struct TouchAuthentication { +struct BioAuthentication { private let authContext: LAContext @@ -50,7 +50,7 @@ struct TouchAuthentication { self.fallbackTitle = fallbackTitle } - func requireTouch(callback: @escaping (Error?) -> Void) { + func validateBiometric(callback: @escaping (Error?) -> Void) { self.authContext.evaluatePolicy(LAPolicy.deviceOwnerAuthenticationWithBiometrics, localizedReason: self.title) { guard $1 == nil else { return callback($1) } callback($0 ? nil : LAError(LAError.authenticationFailed)) diff --git a/Auth0/CredentialsManager.swift b/Auth0/CredentialsManager.swift index 08b8bd40..5fe83cb7 100644 --- a/Auth0/CredentialsManager.swift +++ b/Auth0/CredentialsManager.swift @@ -30,7 +30,7 @@ public struct CredentialsManager { private let storage = A0SimpleKeychain() private let storeKey = "credentials" private let authentication: Authentication - private var touchAuth: TouchAuthentication? + private var bioAuth: BioAuthentication? /// Creates a new CredentialsManager instance /// @@ -40,14 +40,25 @@ public struct CredentialsManager { self.authentication = authentication } - /// Enable TouchID Authentication for additional securtity during credentials retrieval. + /// Enable Touch ID Authentication for additional securtity during credentials retrieval. /// /// - Parameters: /// - title: main message to display in TouchID prompt /// - cancelTitle: cancel message to display in TouchID prompt (iOS 10+) /// - fallbackTitle: fallback message to display in TouchID prompt after a failed match + @available(*, deprecated, message: "see enableBiometrics(withTitle title:, cancelTitle:, fallbackTitle:)") public mutating func enableTouchAuth(withTitle title: String, cancelTitle: String? = nil, fallbackTitle: String? = nil) { - self.touchAuth = TouchAuthentication(authContext: LAContext(), title: title, cancelTitle: cancelTitle, fallbackTitle: fallbackTitle) + self.enableBiometrics(withTitle: title, cancelTitle: cancelTitle, fallbackTitle: fallbackTitle) + } + + /// Enable Biometric Authentication for additional securtity during credentials retrieval. + /// + /// - Parameters: + /// - title: main message to display when Touch ID is used + /// - cancelTitle: cancel message to display when Touch ID is used (iOS 10+) + /// - fallbackTitle: fallback message to display when Touch ID is used after a failed match + public mutating func enableBiometrics(withTitle title: String, cancelTitle: String? = nil, fallbackTitle: String? = nil) { + self.bioAuth = BioAuthentication(authContext: LAContext(), title: title, cancelTitle: cancelTitle, fallbackTitle: fallbackTitle) } /// Store credentials instance in keychain @@ -70,7 +81,7 @@ public struct CredentialsManager { /// - Returns: if there are valid and non-expired credentials stored public func hasValid() -> Bool { guard - let data = self.storage.data(forKey:self.storeKey), + let data = self.storage.data(forKey: self.storeKey), let credentials = NSKeyedUnarchiver.unarchiveObject(with: data) as? Credentials, credentials.accessToken != nil, let expiresIn = credentials.expiresIn @@ -96,9 +107,10 @@ public struct CredentialsManager { /// - Important: This method only works for a refresh token obtained after auth with OAuth 2.0 API Authorization. /// - Note: [Auth0 Refresh Tokens Docs](https://auth0.com/docs/tokens/refresh-token) public func credentials(withScope scope: String? = nil, callback: @escaping (CredentialsManagerError?, Credentials?) -> Void) { - if let touchAuth = self.touchAuth { - guard touchAuth.available else { return callback(.touchFailed(LAError(LAError.touchIDNotAvailable)), nil) } - touchAuth.requireTouch { + guard self.hasValid() else { return callback(.noCredentials, nil) } + if let bioAuth = self.bioAuth { + guard bioAuth.available else { return callback(.touchFailed(LAError(LAError.touchIDNotAvailable)), nil) } + bioAuth.validateBiometric { guard $0 == nil else { return callback(.touchFailed($0!), nil) } @@ -111,7 +123,7 @@ public struct CredentialsManager { private func retrieveCredentials(withScope scope: String? = nil, callback: @escaping (CredentialsManagerError?, Credentials?) -> Void) { guard - let data = self.storage.data(forKey:self.storeKey), + let data = self.storage.data(forKey: self.storeKey), let credentials = NSKeyedUnarchiver.unarchiveObject(with: data) as? Credentials else { return callback(.noCredentials, nil) } guard let expiresIn = credentials.expiresIn else { return callback(.noCredentials, nil) } diff --git a/Auth0/Identity.swift b/Auth0/Identity.swift index 8aab984a..3eec7c33 100644 --- a/Auth0/Identity.swift +++ b/Auth0/Identity.swift @@ -54,7 +54,7 @@ public class Identity: NSObject, JSONObjectPayload { self.accessTokenSecret = accessTokenSecret } - @objc convenience public required init?(json: [String : Any]) { + @objc convenience public required init?(json: [String: Any]) { guard let identifier = json["user_id"] as? String, diff --git a/Auth0/Logger.swift b/Auth0/Logger.swift index f0fe7451..310725b3 100644 --- a/Auth0/Logger.swift +++ b/Auth0/Logger.swift @@ -56,7 +56,7 @@ struct DefaultLogger: Logger { request.allHTTPHeaderFields?.forEach { key, value in output.log(message: "\(key): \(value)") } if let data = request.httpBody, let string = String(data: data, encoding: .utf8) { output.newLine() - output.log(message:string) + output.log(message: string) } output.newLine() } diff --git a/Auth0/ManagementError.swift b/Auth0/ManagementError.swift index f9ffcea9..ae54f08d 100644 --- a/Auth0/ManagementError.swift +++ b/Auth0/ManagementError.swift @@ -86,7 +86,7 @@ extension ManagementError: CustomNSError { public static let infoKey = "com.auth0.management.error.info" public static var errorDomain: String { return "com.auth0.management" } public var errorCode: Int { return 1 } - public var errorUserInfo: [String : Any] { + public var errorUserInfo: [String: Any] { return [ NSLocalizedDescriptionKey: self.description, ManagementError.infoKey: self diff --git a/Auth0/OAuth2Grant.swift b/Auth0/OAuth2Grant.swift index 2f414063..8a9fd8aa 100644 --- a/Auth0/OAuth2Grant.swift +++ b/Auth0/OAuth2Grant.swift @@ -30,7 +30,7 @@ protocol OAuth2Grant { struct ImplicitGrant: OAuth2Grant { - let defaults: [String : String] + let defaults: [String: String] let responseType: [ResponseType] init(responseType: [ResponseType] = [.token], nonce: String? = nil) { @@ -42,7 +42,7 @@ struct ImplicitGrant: OAuth2Grant { } } - func credentials(from values: [String : String], callback: @escaping (Result) -> Void) { + func credentials(from values: [String: String], callback: @escaping (Result) -> Void) { guard validate(responseType: self.responseType, token: values["id_token"], nonce: self.defaults["nonce"]) else { return callback(.failure(error: WebAuthError.invalidIdTokenNonce)) } @@ -51,10 +51,10 @@ struct ImplicitGrant: OAuth2Grant { return callback(.failure(error: WebAuthError.missingAccessToken)) } - callback(.success(result: Credentials(json: values as [String : Any]))) + callback(.success(result: Credentials(json: values as [String: Any]))) } - func values(fromComponents components: URLComponents) -> [String : String] { + func values(fromComponents components: URLComponents) -> [String: String] { return components.a0_fragmentValues } @@ -64,7 +64,7 @@ struct PKCE: OAuth2Grant { let authentication: Authentication let redirectURL: URL - let defaults: [String : String] + let defaults: [String: String] let verifier: String let responseType: [ResponseType] @@ -114,7 +114,7 @@ struct PKCE: OAuth2Grant { } } - func values(fromComponents components: URLComponents) -> [String : String] { + func values(fromComponents components: URLComponents) -> [String: String] { var items = components.a0_fragmentValues components.a0_queryValues.forEach { items[$0] = $1 } return items diff --git a/Auth0/SafariAuthenticationCallback.swift b/Auth0/SafariAuthenticationCallback.swift index 93a98af9..564b77cc 100644 --- a/Auth0/SafariAuthenticationCallback.swift +++ b/Auth0/SafariAuthenticationCallback.swift @@ -40,7 +40,7 @@ class SafariAuthenticationSessionCallback: AuthTransaction { self.authSession?.start() } - func resume(_ url: URL, options: [UIApplicationOpenURLOptionsKey : Any]) -> Bool { + func resume(_ url: URL, options: [UIApplicationOpenURLOptionsKey: Any]) -> Bool { self.callback(true) return true } diff --git a/Auth0/WebAuthError.swift b/Auth0/WebAuthError.swift index 7610da7b..ab5a6d8c 100644 --- a/Auth0/WebAuthError.swift +++ b/Auth0/WebAuthError.swift @@ -58,7 +58,7 @@ public enum WebAuthError: CustomNSError { } } - public var errorUserInfo: [String : Any] { + public var errorUserInfo: [String: Any] { switch self { case .userCancelled: return [ diff --git a/Auth0/_ObjectiveManagementAPI.swift b/Auth0/_ObjectiveManagementAPI.swift index e667ab4e..2eb09e08 100644 --- a/Auth0/_ObjectiveManagementAPI.swift +++ b/Auth0/_ObjectiveManagementAPI.swift @@ -71,7 +71,7 @@ public class _ObjectiveManagementAPI: NSObject { @objc(unlinkUserWithIdentifier:provider:fromUserId:callback:) public func unlink(identifier: String, provider: String, fromUserId userId: String, callback: @escaping (NSError?, [[String: Any]]?) -> Void) { self.users - .unlink(identityId: identifier, provider: provider, fromUserId:userId) + .unlink(identityId: identifier, provider: provider, fromUserId: userId) .start { result in switch result { case .success(let payload): diff --git a/Auth0Tests/TouchAuthenticationSpec.swift b/Auth0Tests/BioAuthenticationSpec.swift similarity index 82% rename from Auth0Tests/TouchAuthenticationSpec.swift rename to Auth0Tests/BioAuthenticationSpec.swift index cb0dc548..08c5f588 100644 --- a/Auth0Tests/TouchAuthenticationSpec.swift +++ b/Auth0Tests/BioAuthenticationSpec.swift @@ -1,4 +1,4 @@ -// TouchAuthenticationSpec.swift +// BioAuthenticationSpec.swift // // Copyright (c) 2016 Auth0 (http://auth0.com) // @@ -27,27 +27,27 @@ import LocalAuthentication @testable import Auth0 -class TouchAuthenticationSpec: QuickSpec { +class BioAuthenticationSpec: QuickSpec { override func spec() { var mockContext: MockLAContext! - var touchAuthentication: TouchAuthentication! + var bioAuthentication: BioAuthentication! beforeEach { mockContext = MockLAContext() - touchAuthentication = TouchAuthentication(authContext: mockContext, title: "Touch Auth") + bioAuthentication = BioAuthentication(authContext: mockContext, title: "Touch Auth") } describe("touch availablility") { it("touch should be enabled") { - expect(touchAuthentication.available).to(beTrue()) + expect(bioAuthentication.available).to(beTrue()) } it("touch should be disabled") { mockContext.enabled = false - expect(touchAuthentication.available).to(beFalse()) + expect(bioAuthentication.available).to(beFalse()) } } @@ -55,13 +55,13 @@ class TouchAuthenticationSpec: QuickSpec { if #available(iOS 10, *) { it("should set cancel title") { - touchAuthentication.cancelTitle = "cancel title" + bioAuthentication.cancelTitle = "cancel title" expect(mockContext.localizedCancelTitle) == "cancel title" } } it("should set fallback title") { - touchAuthentication.fallbackTitle = "fallback title" + bioAuthentication.fallbackTitle = "fallback title" expect(mockContext.localizedFallbackTitle) == "fallback title" } } @@ -74,21 +74,21 @@ class TouchAuthenticationSpec: QuickSpec { } it("should authenticate") { - touchAuthentication.requireTouch { error = $0 } + bioAuthentication.validateBiometric { error = $0 } expect(error).toEventually(beNil()) } it("should return error on touch authentication") { let touchError = LAError(.appCancel) mockContext.replyError = touchError - touchAuthentication.requireTouch { error = $0 } + bioAuthentication.validateBiometric { error = $0 } expect(error).toEventually(matchError(touchError)) } it("should return authenticationFailed error if no policy success") { let touchError = LAError(.authenticationFailed) mockContext.replySuccess = false - touchAuthentication.requireTouch { error = $0 } + bioAuthentication.validateBiometric { error = $0 } expect(error).toEventually(matchError(touchError)) } diff --git a/Auth0Tests/CredentialsManagerSpec.swift b/Auth0Tests/CredentialsManagerSpec.swift index 6dfb6bbb..b69e3a43 100644 --- a/Auth0Tests/CredentialsManagerSpec.swift +++ b/Auth0Tests/CredentialsManagerSpec.swift @@ -134,11 +134,11 @@ class CredentialsManagerSpec: QuickSpec { expect(newCredentials).toEventually(beNil()) } - it("should error when no refresh_token present and token expired") { + it("should error when token expired") { credentials = Credentials(accessToken: AccessToken, tokenType: TokenType, idToken: IdToken, refreshToken: nil, expiresIn: Date(timeIntervalSinceNow: -ExpiresIn)) _ = credentialsManager.store(credentials: credentials) credentialsManager.credentials { error = $0; newCredentials = $1 } - expect(error).to(matchError(CredentialsManagerError.noRefreshToken)) + expect(error).to(matchError(CredentialsManagerError.noCredentials)) expect(newCredentials).toEventually(beNil()) } @@ -160,10 +160,13 @@ class CredentialsManagerSpec: QuickSpec { context("require touch") { beforeEach { - credentialsManager.enableTouchAuth(withTitle: "Auth Title", cancelTitle: "Cancel Title", fallbackTitle: "Fallback Title") + credentialsManager.enableBiometrics(withTitle: "Auth Title", cancelTitle: "Cancel Title", fallbackTitle: "Fallback Title") } it("should error when touch unavailable") { + credentials = Credentials(accessToken: AccessToken, tokenType: TokenType, idToken: IdToken, refreshToken: RefreshToken, expiresIn: Date(timeIntervalSinceNow: -3600)) + _ = credentialsManager.store(credentials: credentials) + waitUntil(timeout: 2) { done in credentialsManager.credentials { error = $0; newCredentials = $1 expect(error).to(matchError(CredentialsManagerError.touchFailed(LAError(LAError.touchIDNotAvailable)))) @@ -172,7 +175,6 @@ class CredentialsManagerSpec: QuickSpec { } } } - } context("renew") { diff --git a/README.md b/README.md index e221afaf..21641d13 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,8 @@ Auth0 ### Credentials Management Utility (iOS Only) +The credentials manager utility provides a convenience to securely store and retrieve the user's credentials from the Keychain. + ```swift let credentialsManager = CredentialsManager(authentication: Auth0.authentication()) ``` @@ -198,7 +200,9 @@ Store user credentials securely in the KeyChain. credentialsManager.store(credentials: credentials) ``` -#### Retrieve stored credentials and auto-renew if expired +#### Retrieve stored credentials + +Credentials will automatically be renewed (if expired) using the refresh token. The scope `offline_access` is required to ensure the refresh token is returned. ```swift credentialsManager.credentials { error, credentials in @@ -207,6 +211,14 @@ credentialsManager.credentials { error, credentials in } ``` +#### Biometric authentication + +You can enable an additional level of user authentication before retrieving credentials using the biometric authentication supported by your device e.g. Face ID or Touch ID. + +```swift +credentialsManager.enableBiometrics(withTitle: "Touch to Login") +``` + ### Authentication API (iOS / macOS / tvOS) The Authentication API exposes AuthN/AuthZ functionality of Auth0, as well as the supported identity protocols like OpenID Connect, OAuth 2.0, and SAML. diff --git a/circle.yml b/circle.yml index 1d4e9e60..e24deed6 100644 --- a/circle.yml +++ b/circle.yml @@ -1,6 +1,6 @@ machine: xcode: - version: 9.0 + version: 9.2 environment: SCHEME: "Auth0.iOS" DEVICE: "iPhone 8"