Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CredentialsManager function to clear and revoke the refresh token #312

Merged
merged 14 commits into from
Oct 15, 2019
Merged
28 changes: 28 additions & 0 deletions Auth0/CredentialsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,34 @@ public struct CredentialsManager {
return self.storage.deleteEntry(forKey: storeKey)
}

/// Calls the revoke token endpoint to revoke the refresh token and, if successful, the credentials are cleared. Otherwise,
/// the credentials are not cleared and an error is raised through the callback.
lbalmaceda marked this conversation as resolved.
Show resolved Hide resolved
///
/// If no refresh token is available the endpoint is not called, the credentials are cleared, and the callback is invoked without an error.
///
/// - Parameter callback: callback with an error if the refresh token could not be revoked
public func revoke(_ callback: @escaping (CredentialsManagerError?) -> Void) {
guard
let data = self.storage.data(forKey: self.storeKey),
let credentials = NSKeyedUnarchiver.unarchiveObject(with: data) as? Credentials,
let refreshToken = credentials.refreshToken else {
_ = self.clear()
return callback(nil)
}

self.authentication
.revoke(refreshToken: refreshToken)
.start { result in
switch result {
case .failure(let error):
callback(CredentialsManagerError.revokeFailed(error))
case .success:
_ = self.clear()
callback(nil)
}
}
}

/// Checks if a non-expired set of credentials are stored
///
/// - Returns: if there are valid and non-expired credentials stored
Expand Down
1 change: 1 addition & 0 deletions Auth0/CredentialsManagerError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ public enum CredentialsManagerError: Error {
case noRefreshToken
case failedRefresh(Error)
case touchFailed(Error)
case revokeFailed(Error)
}
70 changes: 70 additions & 0 deletions Auth0Tests/CredentialsManagerSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,76 @@ class CredentialsManagerSpec: QuickSpec {
}
}

describe("clearing and revoking refresh token") {

beforeEach {
_ = credentialsManager.store(credentials: credentials)

stub(condition: isRevokeToken(Domain) && hasAtLeast(["token": RefreshToken])) { _ in return revokeTokenResponse() }.name = "revoke success"
}

afterEach {
_ = credentialsManager.clear()
}

it("should clear credentials and revoke the refresh token") {
waitUntil(timeout: 2) { done in
credentialsManager.revoke {
expect($0).to(beNil())
expect(credentialsManager.hasValid()).to(beFalse())
done()
}
}
}

it("should not return an error if there were no credentials stored") {
_ = credentialsManager.clear()

waitUntil(timeout: 2) { done in
credentialsManager.revoke {
expect($0).to(beNil())
expect(credentialsManager.hasValid()).to(beFalse())
done()
}
}
}

it("should not return an error if there is no refresh token, and clear credentials anyway") {
let credentials = Credentials(
accessToken: AccessToken,
idToken: IdToken,
expiresIn: Date(timeIntervalSinceNow: ExpiresIn)
)

_ = credentialsManager.store(credentials: credentials)

waitUntil(timeout: 2) { done in
credentialsManager.revoke {
expect($0).to(beNil())
expect(credentialsManager.hasValid()).to(beFalse())
done()
}
}
}

it("should return the failure if the token could not be revoked, and not clear credentials") {
stub(condition: isRevokeToken(Domain) && hasAtLeast(["token": RefreshToken])) { _ in
return authFailure(code: "400", description: "Revoke failed")
}

waitUntil(timeout: 2) { done in
credentialsManager.revoke {
expect($0).to(matchError(
CredentialsManagerError.revokeFailed(AuthenticationError(string: "Revoke failed", statusCode: 400))
))

expect(credentialsManager.hasValid()).to(beTrue())
done()
}
}
}
}

describe("multi instances of credentials manager") {

var secondaryCredentialsManager: CredentialsManager!
Expand Down
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,26 @@ credentialsManager.credentials { error, credentials in
}
```

#### Clearing credentials and revoking refresh tokens

Credentials can be cleared by using the `clear` function, which clears credentials from the keychain:

```swift
let didClear = credentialsManager.clear()
```

In addition, credentials can be cleared and the refresh token revoked using a single call to `revoke`. This function will attempt to revoke the current refresh token stored by the credential manager and then clear credentials from the keychain. If revoking the token results in an error, then the credentials are not cleared:

```swift
credentialsManager.revoke { error in
guard error == nil else {
return print("Failed to revoke refresh token: \(error)")
}

print("Success")
}
```

#### 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.
Expand Down