Skip to content

Add support for disabling credential auto-renewal #980

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

NachoSoto
Copy link

@NachoSoto NachoSoto commented Jul 23, 2025

  • All new/changed/fixed functionality is covered by tests (or N/A)
  • I have added documentation for all new/changed functionality (or N/A)

📋 Changes

  • Added new optional parameter to CredentialsManager constructor (not changing default behavior) to allow disabling credential renewals.
  • This is useful to be able to use Auth0 from an extension, where we wouldn't want requests to automatically refresh credentials potentially leading to race conditions with the main app target doing the same.

@NachoSoto NachoSoto requested a review from a team as a code owner July 23, 2025 18:20
@NachoSoto NachoSoto force-pushed the nacho/disable-auto-refresh branch 2 times, most recently from 95cbae6 to 0734b2b Compare July 23, 2025 18:21
@NachoSoto NachoSoto force-pushed the nacho/disable-auto-refresh branch from 0734b2b to 2fab17b Compare July 23, 2025 18:22
@@ -240,12 +244,13 @@ public struct CredentialsManager {
/// - Returns: If there are credentials stored containing a refresh token.
public func canRenew() -> Bool {
guard let credentials = self.retrieveCredentials() else { return false }
return credentials.refreshToken != nil
return self.allowsAutoRefreshing && credentials.refreshToken != nil

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe check allowsAutoRefreshing before self.retrieveCredentials() ?

@@ -652,6 +657,11 @@ public struct CredentialsManager {
dispatchGroup.leave()
return callback(.success(credentials))
}

guard self.allowsAutoRefreshing else {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if this is right. This will prevent refreshing, but that's not the only thing the code below does, it also tries to read the credentials from the store/keychain

@Widcket
Copy link
Contributor

Widcket commented Jul 24, 2025

Hi @NachoSoto, thanks for raising this.

This is useful to be able to use Auth0 from an extension, where we wouldn't want requests to automatically refresh credentials potentially leading to race conditions with the main app target doing the same.

Would configuring the Rotation Overlap Period work for this purpose? Given two concurrent renewal requests (one from the app and one from the extension), once the first succeeds, if the second one completes within the rotation overlap period, it will succeed as well, and no token rotation error will be returned.

Screenshot 2025-07-24 at 10 43 35

See https://auth0.com/docs/secure/tokens/refresh-tokens/configure-refresh-token-rotation#configure-in-the-dashboard for more information.

You can also configure a lower timeout value to ensure that the second request either succeeds or fails within the rotation overlap period. To do so, you need to use a custom URLSession:

var configuration = URLSessionConfiguration.default
// configure it...

let customURLSession = URLSession(configuration: configuration)
let auth = Auth0.authentication(session: customURLSession)
let credentialsManager = CredentialsManager(authentication: auth)

@NachoSoto
Copy link
Author

Given two concurrent renewal requests (one from the app and one from the extension), once the first succeeds

Are there any guarantees that the two can safely complete concurrently? And after that, there could be a race condition where the first one that completed is the one that ends up in the keychain, leading to a broken token, right?

@Widcket
Copy link
Contributor

Widcket commented Jul 24, 2025

Are there any guarantees that the two can safely complete concurrently? And after that, there could be a race condition where the first one that completed is the one that ends up in the keychain, leading to a broken token, right?

The first one that completes will be automatically saved in the Keychain by the Credentials Manager. But, if the rotation overlap period is configured, then the second request will still succeed, and will also be automatically saved in the Keychain.

I just tried it out, and exchanging the same refresh token concurrently yielded the same access token, so there should be no issues on that front.

@Widcket
Copy link
Contributor

Widcket commented Jul 29, 2025

@NachoSoto did setting the rotation overlap period work for your use case?

@NachoSoto
Copy link
Author

I haven't had a chance yet. But even if it handles race conditions correctly with 2 processes potentially writing in the keychain simultaneously, we would still prefer to run Auth0 from an app extension in a read-only configuration (which is what we'd accomplish with this PR).
Does that makes sense?

@Widcket
Copy link
Contributor

Widcket commented Aug 6, 2025

I'd suggest trying out the rotation overlap period first and seeing if you run into any issues.

@Widcket
Copy link
Contributor

Widcket commented Aug 6, 2025

FYI I won't be working as a maintainer of this library anymore. In the future please work with @NandanPrabhu.

@NachoSoto
Copy link
Author

How can you guarantee there can’t be race conditions with 2 processes writing into the keychain though?

@Widcket
Copy link
Contributor

Widcket commented Aug 7, 2025

How can you guarantee there can’t be race conditions with 2 processes writing into the keychain though?

It seems I did not explain the purpose of the Rotation Overlap Period setting clearly. My bad. Let's try again.

Configuring this value (e.g., to 30 seconds), along with a shorter timeout value (e.g., 5 seconds), means:

  • Both processes will still send concurrent renewal requests.
  • Both concurrent renewal requests will succeed.
  • Both responses will come with the same access token.
  • The same access token will be written twice to the Keychain. The SecItem APIs are atomic, and the locks that make it possible are managed by the OS. See https://developer.apple.com/forums/thread/117092?answerId=361922022#361922022.

In other words, this will not prevent concurrent renewal requests; it will make the concurrency irrelevant.

@Widcket
Copy link
Contributor

Widcket commented Aug 7, 2025

If not, please feel free to create a reproduction (e.g., a minimal app with an extension, both writing to the Keychain in a loop) and share it with us.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants