Skip to content

Instantly share code, notes, and snippets.

@jianghaolu
Last active April 7, 2020 20:33
Show Gist options
  • Save jianghaolu/48518472d0f236605538e4f2a8fe638e to your computer and use it in GitHub Desktop.
Save jianghaolu/48518472d0f236605538e4f2a8fe638e to your computer and use it in GitHub Desktop.
Cross-plat shared token cache

Goal of the proposal

  • Discuss user experience of current design
  • Discuss perf results and possible bottlenecks

Usage

Scenario 1: Use an existing token from an existing cache

SharedTokenCacheCredential credential = new SharedTokenCacheCredentialBuilder().clientId(clientId).build();
SecretClient secretClient = new SecretClientBuilder()
    .vaultUrl("https://my-vault.vault.azure.net")
    .credential(credential)
    .build();

It automatically refreshes the token when the access token expires and updates the token cache with the refreshed access token.

Scenario 2: Authenticating and putting a new token into cache

InteractiveBrowserCredential credential = new InteractiveBrowserCredentialBuilder().clientId(clientId).port(8765).build();
SecretClient secretClient = new SecretClientBuilder()
    .vaultUrl("https://my-vault.vault.azure.net")
    .credential(credential)
    .build();

The credential fetches a new access token and refresh token for the user and writes to the token cache

Configurations

Cache file location - Windows DPAPI protected

SharedTokenCacheCredential credential = new SharedTokenCacheCredentialBuilder()
    .clientId(clientId)
    .cacheFileLocation(Paths.get(System.getProperty("user.home"), "AppData", "Local", ".IdentityService", "msal.cache"))
    .build();

Keychain - MacOS (OS X)

SharedTokenCacheCredential credential = new SharedTokenCacheCredentialBuilder()
    .clientId(clientId)
    .keychainService("Microsoft.Developer.IdentityService")
    .keychainAccount("MSALCache")
    .build();

Keyring - Linux Gnome

SharedTokenCacheCredential credential = new SharedTokenCacheCredentialBuilder()
    .clientId(clientId)
    .keyringName("default")
    .keyringItemSchema("org.freedesktop.Secret.Generic")
    .keyringItemName("MSALCache")
    .addKeyringAttribute("MsalClientID", ""Microsoft.Developer.IdentityService")
    .build();

Linux unprotected cache file

SharedTokenCacheCredential credential = new SharedTokenCacheCredentialBuilder()
    .clientId(clientId)
    .cacheFileLocation(Paths.get(System.getProperty("user.home"), ".IdentityService", "msal.cache"))
    .useUnprotectedFileOnLinux(true)
    .build();

Disable shared token cache

Shared token cache read/write ops are enabled by default(?). To turn it off:

SharedTokenCacheCredential credential = new SharedTokenCacheCredentialBuilder()
    .clientId(clientId)
    .disableSharedTokenCache(true)
    .build();

Default values

  • Default Windows cache file: {user.home}/AppData/Local/.IdentityService/msal.cache
  • Default Mac Keychain entry: Microsoft.Developer.IdentityService/MSALCache
  • Default Gnome Keyring entry(?): MSALCache with attribute MsalClientID: Microsoft.Developer.IdentityService
  • Default Linux unprotected file(?): {user.home}/.IdentityService/msal.cache

Design questions

  • Default values are applied when no configuration is made vs default values are only applied on DefaultAzureCredential
  • Shared token cache read/write is enabled by default vs disabled by default

Performance results

  • Windows DPAPI testing (i7,32G,SSD)
    • Single process: read: ~7500 RPS
    • Single process: write: ~6 WPS
    • Multiple processes: read: <2000 RPS
    • Multiple processes: write: 4.59 WPS
  • Mac Keychain testing (i7,16G,SSD)
    • Single process: read: ~25,000 RPS
    • Single process: write: ~1.5 WPS
    • Multiple processes: read: 3500 RPS
    • Multiple processes: 14 WPS

Known issues

Mac keychain race condition:

Exception in thread "main" com.microsoft.aad.msal4jextensions.persistence.mac.KeyChainAccessException: The specified item already exists in the keychain.
	at com.microsoft.aad.msal4jextensions.persistence.mac.KeyChainAccessor.write(KeyChainAccessor.java:82)
	at com.microsoft.aad.msal4jextensions.PersistenceTokenCacheAccessAspect.afterCacheAccess(PersistenceTokenCacheAccessAspect.java:138)
	at com.microsoft.aad.msal4j.TokenCache$CacheAspect.close(TokenCache.java:168)
	at com.microsoft.aad.msal4j.TokenCache.saveTokens(TokenCache.java:213)
	at com.microsoft.aad.msal4j.ClientApplicationBase.acquireTokenCommon(ClientApplicationBase.java:95)
	at com.microsoft.aad.msal4j.AcquireTokenByAuthorizationGrantSupplier.execute(AcquireTokenByAuthorizationGrantSupplier.java:52)
	at com.microsoft.aad.msal4j.AcquireTokenSilentSupplier.execute(AcquireTokenSilentSupplier.java:50)
	at com.microsoft.aad.msal4j.AuthenticationResultSupplier.get(AuthenticationResultSupplier.java:59)
	at com.microsoft.aad.msal4j.AuthenticationResultSupplier.get(AuthenticationResultSupplier.java:17)
	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1700)
	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1692)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:177)
	Suppressed: java.lang.Exception: #block terminated with an error
		at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:93)
		at reactor.core.publisher.Mono.block(Mono.java:1663)
		at com.azure.perf.test.core.PerfStressProgram.runTests(PerfStressProgram.java:202)
		at com.azure.perf.test.core.PerfStressProgram.run(PerfStressProgram.java:136)
		at com.azure.perf.test.core.PerfStressProgram.run(PerfStressProgram.java:78)
		at com.azure.identity.perf.App.main(App.java:36)

Windows error on Zulu 1.8:

  os::bang_stack_shadow_pages+0x47
#
# Core dump written. Default location: E:\work\azure-sdk-for-java\sdk\identity\perf-test\hs_err_pid2928.mdmp
#
# An error report file with more information is saved as:
# E:\work\azure-sdk-for-java\sdk\identity\perf-test\hs_err_pid2928.log
#
# If you would like to submit a bug report, please visit:
#   http://www.azulsystems.com/support/
#
#
# A fatal error has been detected by the Java Runtime Environment:
#
#  EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x0000000070774af7, pid=24324, tid=0x00000000000078dc
#
# JRE version: OpenJDK Runtime Environment (8.0_192-b01) (build 1.8.0_192-b01)
# Java VM: OpenJDK 64-Bit Server VM (25.192-b01-fastdebug mixed mode windows-amd64 compressed oops)
# Problematic frame:
# V  [jvm.dll+0x2f4af7]  os::bang_stack_shadow_pages+0x47
#
# Core dump written. Default location: E:\work\azure-sdk-for-java\sdk\identity\perf-test\hs_err_pid24324.mdmp
#
# An error report file with more information is saved as:
# E:\work\azure-sdk-for-java\sdk\identity\perf-test\hs_err_pid24324.log
#
# If you would like to submit a bug report, please visit:
#   http://www.azulsystems.com/support/
#
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment