Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions Auth0/CredentialsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,33 @@ public struct CredentialsManager: Sendable {
return self.storage.deleteEntry(forKey: key)
}

/// Clears all credentials stored in the underlying storage, including the main credentials and any API
/// credentials for all audiences.
///
/// This method delegates to the underlying storage to delete **all** of its entries (for example, all
/// Keychain items for the configured service/access group), not just those keys explicitly known to
/// ``CredentialsManager``. If the same storage instance or Keychain service/access group is shared with
/// other parts of your application, calling this method may delete non-Auth0 data as well.
///
/// To avoid unintended data loss, ensure that the storage backing this ``CredentialsManager`` is dedicated
/// to Auth0 credentials when using ``clearAll()``.
///
/// ## Usage
///
/// ```swift
/// try credentialsManager.clearAll()
/// ```
///
/// - Throws: An error when the delete operation fails.
public func clearAll() throws {
#if WEB_AUTH_PLATFORM
self.biometricSession.lock.lock()
self.biometricSession.lastBiometricAuthTime = self.biometricSession.noSession
self.biometricSession.lock.unlock()
#endif
try self.storage.deleteAllEntries()
}

#if WEB_AUTH_PLATFORM
/// Checks if the current biometric session is valid based on the configured policy.
///
Expand Down
21 changes: 21 additions & 0 deletions Auth0/CredentialsStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,20 @@ public protocol CredentialsStorage {
/// - Returns: If the entry was deleted.
func deleteEntry(forKey key: String) -> Bool

/// Deletes all storage entries managed by this ``CredentialsStorage`` instance.
///
/// - Throws: An error when the delete operation fails.
func deleteAllEntries() throws

}

extension CredentialsStorage {

/// Default implementation that triggers an assertion failure.
public func deleteAllEntries() throws {
assertionFailure("deleteAllEntries() is not implemented. Implement this method in your custom CredentialsStorage.")
}

}

/// Conformance to ``CredentialsStorage``.
Expand Down Expand Up @@ -65,4 +79,11 @@ extension SimpleKeychain: CredentialsStorage {
}
}

/// Deletes all storage entries from the Keychain for the service and access group values.
///
/// - Throws: A `SimpleKeychainError` when the operation fails.
public func deleteAllEntries() throws {
try self.deleteAll()
}

}
51 changes: 50 additions & 1 deletion Auth0Tests/CredentialsManagerSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,47 @@ class CredentialsManagerSpec: QuickSpec {

}

describe("clearAll") {

afterEach {
try? credentialsManager.clearAll()
}

it("should clear all credentials from keychain") {
let store = SimpleKeychain()
credentialsManager = CredentialsManager(authentication: authentication, storage: store)

expect(credentialsManager.store(credentials: credentials)).to(beTrue())
expect(credentialsManager.store(apiCredentials: apiCredentials, forAudience: Audience)).to(beTrue())

expect { try credentialsManager.clearAll() }.toNot(throwError())
expect(credentialsManager.hasValid()).to(beFalse())
expect(fetchAPICredentials(forAudience: Audience, from: store)).to(beNil())
}

it("should not throw when keychain is already empty") {
let store = SimpleKeychain()
credentialsManager = CredentialsManager(authentication: authentication, storage: store)

expect { try credentialsManager.clearAll() }.toNot(throwError())
}

it("should throw when storage fails") {
class FailingStore: CredentialsStorage {
func getEntry(forKey key: String) -> Data? { return nil }
func setEntry(_ data: Data, forKey key: String) -> Bool { return true }
func deleteEntry(forKey key: String) -> Bool { return true }
func deleteAllEntries() throws {
throw NSError(domain: "test", code: -1)
}
}

credentialsManager = CredentialsManager(authentication: authentication, storage: FailingStore())
expect { try credentialsManager.clearAll() }.to(throwError())
}

}

describe("custom storage") {

class CustomStore: CredentialsStorage {
Expand All @@ -215,6 +256,9 @@ class CredentialsManagerSpec: QuickSpec {
store[key] = nil
return true
}
func deleteAllEntries() throws {
store.removeAll()
}
}

beforeEach {
Expand Down Expand Up @@ -700,6 +744,7 @@ class CredentialsManagerSpec: QuickSpec {
func deleteEntry(forKey: String) -> Bool {
return true
}
func deleteAllEntries() throws {}
}

credentialsManager = CredentialsManager(authentication: authentication, storage: MockStore())
Expand Down Expand Up @@ -1639,7 +1684,7 @@ class CredentialsManagerSpec: QuickSpec {
scope: Scope)
return try? apiCredentials.encode()
}

return encodeCredentials(Credentials(refreshToken: RefreshToken))
}
func setEntry(_ data: Data, forKey: String) -> Bool {
Expand All @@ -1648,6 +1693,7 @@ class CredentialsManagerSpec: QuickSpec {
func deleteEntry(forKey: String) -> Bool {
return true
}
func deleteAllEntries() throws {}
}

_ = credentialsManager.store(credentials: credentials)
Expand Down Expand Up @@ -2097,6 +2143,8 @@ class CredentialsManagerSpec: QuickSpec {
func deleteEntry(forKey: String) -> Bool {
return true
}

func deleteAllEntries() throws {}
}

credentialsManager = CredentialsManager(authentication: authentication, storage: MockStore())
Expand Down Expand Up @@ -2342,6 +2390,7 @@ class CredentialsManagerSpec: QuickSpec {
func deleteEntry(forKey: String) -> Bool {
return true
}
func deleteAllEntries() throws {}
}

credentialsManager = CredentialsManager(authentication: authentication, storage: MockStore())
Expand Down
17 changes: 17 additions & 0 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -926,6 +926,23 @@ The stored credentials can be removed from the Keychain by using the `clear()` m
let didClear = credentialsManager.clear()
```

### Clear all stored credentials

To remove **all** credentials stored by the Credentials Manager from the Keychain β€”including the default credentials entry and any API credentials stored for different audiencesβ€” use the `clearAll()` method.

```swift
do {
try credentialsManager.clearAll()
} catch {
print("Failed to clear all credentials: \(error)")
}
```

> [!NOTE]
> `clearAll()` delegates to the underlying storage's `deleteAllEntries()` method, which removes all entries for the configured service/access group. Ensure the storage is dedicated to Auth0 credentials to avoid unintended data loss.

This is different from `clear()`, which only removes the default credentials entry.

### Biometric authentication

You can enable an additional level of user authentication before retrieving credentials using the biometric authentication supported by the device, such as Face ID or Touch ID.
Expand Down
60 changes: 60 additions & 0 deletions V3_MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ As expected with a major release, Auth0.swift v3 contains breaking changes. Plea
+ [Completion callbacks](#completion-callbacks)
- [**Methods Added**](#methods-added)
+ [Web Auth](#web-auth)
+ [Credentials Manager clearAll](#credentials-manager-clearall)
+ [CredentialsStorage deleteAllEntries](#credentialsstorage-deleteallentries)
- [**Swift 6 Concurrency**](#swift-6-concurrency)
+ [Sendable protocol conformances](#sendable-protocol-conformances)
- [**API Changes**](#api-changes)
Expand Down Expand Up @@ -246,6 +248,64 @@ Auth0
```
</details>

### Credentials Manager clearAll

**New method:** `clearAll() throws` has been added to `CredentialsManager`.

This method removes **all** entries managed by the Credentials Manager from the Keychain (its configured storage/service), including the default credentials entry and any API credentials stored via `store(apiCredentials:)`. It also resets the biometric authentication session (if biometric authentication was enabled).

This is different from the existing `clear()` method, which only removes the default credentials entry.

<details>
<summary>Code</summary>

```swift
// Clear only the default credentials entry (existing method)
let cleared = credentialsManager.clear()

// Clear ALL keychain entries managed by the Credentials Manager (new method)
do {
try credentialsManager.clearAll()
} catch {
print("Failed to clear all credentials: \(error)")
}
```
</details>

**Impact:** This is a new additive API. No migration is required. Use it when you need to completely wipe all stored credentials (e.g., on account deletion or full sign-out).

### CredentialsStorage deleteAllEntries

**New method:** `deleteAllEntries() throws` has been added to the `CredentialsStorage` protocol with a default implementation that triggers an `assertionFailure`.

If you're using a custom `CredentialsStorage` and plan to call `clearAll()`, you'll need to implement `deleteAllEntries()` in your custom storage β€” otherwise it will trigger an assertion failure. If you're not using `clearAll()`, no migration is required.

<details>
<summary>Migration example</summary>

```swift
// v2 - CredentialsStorage protocol
class MyCustomCredentialStorage: CredentialsStorage {
func getEntry(forKey key: String) -> Data? { ... }
func setEntry(_ data: Data, forKey key: String) -> Bool { ... }
func deleteEntry(forKey key: String) -> Bool { ... }
}

// v3 - Implement deleteAllEntries() if you plan to use clearAll()
class MyCustomCredentialStorage: CredentialsStorage {
func getEntry(forKey key: String) -> Data? { ... }
func setEntry(_ data: Data, forKey key: String) -> Bool { ... }
func deleteEntry(forKey key: String) -> Bool { ... }

func deleteAllEntries() throws {
// Delete all entries from your custom storage
}
}
```
</details>

**Impact:** If you have a custom `CredentialsStorage` implementation and use `clearAll()`, you must implement the `deleteAllEntries()` method. If you don't use `clearAll()`, no changes are needed β€” the default implementation will only trigger an assertion if called. The default `SimpleKeychain`-based storage already provides this implementation.

---

## Swift 6 Concurrency
Expand Down
Loading