Skip to content

Added support for alternate TokenStore implementations#1

Closed
atomicgeese wants to merge 1 commit into
AmoreComputer:mainfrom
atomicgeese:tokenStoreAdditions
Closed

Added support for alternate TokenStore implementations#1
atomicgeese wants to merge 1 commit into
AmoreComputer:mainfrom
atomicgeese:tokenStoreAdditions

Conversation

@atomicgeese
Copy link
Copy Markdown

@atomicgeese atomicgeese commented May 27, 2026

I have an application and a QuickLook extension that need to both validate the users license. The QuickLook extension is what people are mostly going to use and the most important thing, the application is primarily the delivery vehicle.

QuickLook extensions are heavily sandboxed, so I explored different storage methods until I found something that worked, the keychain. I tried other directories and App Groups but those were inaccessible from the extension.

This PR introduces support for the user to specify the token store location via a new TokenStoreLocation enum in LicensingConfiguration. Options include:

  • defaultLocation: The classic implementation. Default value for the LicensingConfiguration initializer.
  • directory(URL): A caller specified directory.
  • appGroup(String): An App Group identifier.
  • keychainAccessGroup(String): A keychain Access Group.

Only minor changes were needed beyond that.

  • AmoreLicensing.init now switches on the token store location to setup and appropriate store.
  • FileTokenStore received a new initializer to use an App Group.
  • KeychainTokenStore.swift was added to support the keychain.

KeychainTokenStore supports retrieve/store/delete. The queries are configured to allow access after first unlock, restrict the item to this device only (no iCloud sync), and the "data protection" keychain (the "login" keychain in Keychain Access). That last part is important as it's the only keychain extensions can access. Without that items are stored in the "Local Items" or "iCloud" keychain which is inaccessible from extensions.

With these changes I can add/validate/delete licenses in the app, and validate the license from the extension.

All that said, the only method I need for my use case is keychain support. If you don't want to add support for the other included methods, those could be easily deleted.

An alternative implementation would be to make the TokenStore protocol public and allow the caller to pass in their own implementation, thus relieving you of having to maintain additional code.

Unfortunately, it is not possible (as far as I know) to unit test the App Group or Access Group solutions from a pure swift package setup. Those methods require the test host app to have specific entitlements configured, and package unit tests are hosted by Xcode in a generic faceless app that is the same for all packages.

It should be possible to add unit tests to the Pomodoro test app if that was desired.

Added support to LicensingConfiguration to allow for specifying what kind of token store is used.

Updated AmoreLicensing to use the configuration changes to setup  the new token stores.

Added support for App Groups to FileTokenStore.

Added a new KeychainTokenStore that will load/save/delete from the macOS keychain.
@atomicgeese atomicgeese marked this pull request as ready for review May 27, 2026 22:36
@lucasfischer
Copy link
Copy Markdown
Contributor

First of all, thank you so much for your work on this. This is the first PR on this repository and it means a lot to me. ❤️

Your writeup is excellent and your investigation is spot on.

I went with the alternative you suggested and made the TokenStore public and letting callers inject their own implementation. It keeps entitlement-specific code (keychain accessibility, access groups, service naming) in the app that actually owns those entitlements, and it means storage backends can grow without changes to the library. That's shipping in #2, which credits you and lists you as co-author on the commit.

Your KeychainTokenStore is genuinely useful, so rather than let it disappear with this PR I'd love to include it as a documented recipe in the sample app (co-authored to you), so anyone with the same app + extension setup has a working reference. Happy to do that, or open it up if you'd prefer to.

Closing this in favor of #2, but the credit for the direction is yours. Thanks again ❤️

@atomicgeese
Copy link
Copy Markdown
Author

I suspected #2 would be the best way forward for you, glad that worked out. I also appreciate the credit!

Since this repo and Pomodoro are MIT licensed, feel free to add code from this closed PR as sample code that may help someone else out.

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