Closed FelixZY closed 2 weeks ago
@billwert since you closed https://github.com/Azure/azure-sdk-for-java/issues/28186 can you please follow up with @FelixZY
Hello!
This is a doc bug which we'll fix. Calling authenticate
unconditionally will always cause a challenge, as we don't know that you are wanting to authenticate with the same user.
Here's what I get from the below program: Run 1:
No auth record
To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code <redacted> to authenticate.
Logged in!
Run 2:
Logged in!
String authRecord = "authrecord.bin";
AuthenticationRecord record = null;
try {
record = AuthenticationRecord.deserialize(new FileInputStream(authRecord));
} catch (FileNotFoundException e) {
System.out.println("No auth record");
}
DeviceCodeCredential cred = new DeviceCodeCredentialBuilder()
.tokenCachePersistenceOptions(new TokenCachePersistenceOptions())
.authenticationRecord(record)
.clientId("clientId")
.tenantId("tenantId")
.challengeConsumer(challenge -> System.out.println(challenge.getMessage()))
.build();
TokenRequestContext ctx = new TokenRequestContext().addScopes("https://graph.microsoft.com/User.Read");
if (record == null) {
// Perform the authentication and save the record
AuthenticationRecord newRecord = cred.authenticate(ctx).block();
try {
newRecord.serialize(new FileOutputStream(authRecord));
} catch (FileNotFoundException e) {
System.out.println("couldn't serialize" + e.getMessage());
}
}
AccessToken token = cred.getTokenSync(ctx);
if (token.getToken() != null) {
System.out.println("Logged in!");
}
Updated Kotlin snippet:
package com.example
import com.azure.core.credential.TokenRequestContext
import com.azure.identity.AuthenticationRecord
import com.azure.identity.DeviceCodeCredential
import com.azure.identity.DeviceCodeCredentialBuilder
import com.azure.identity.TokenCachePersistenceOptions
import java.io.FileInputStream
import java.io.FileOutputStream
fun main(args: Array<String>) {
val authRecordFilePath = "./tokencache.bin"
val authenticationRecord: AuthenticationRecord? =
runCatching {
AuthenticationRecord.deserialize(FileInputStream(authRecordFilePath))
}.getOrNull()
println("authenticationRecord is null: ${authenticationRecord == null}")
val credential: DeviceCodeCredential = DeviceCodeCredentialBuilder()
.tokenCachePersistenceOptions(TokenCachePersistenceOptions())
.authenticationRecord(authenticationRecord)
.clientId("<redacted>")
.tenantId("<redacted>")
.challengeConsumer {
println(it.message)
}
.build()
val context = TokenRequestContext().addScopes("https://graph.microsoft.com/.default")
if (authenticationRecord == null) {
credential.authenticate(context).block()?.let { authenticationRecord: AuthenticationRecord ->
authenticationRecord.serialize(FileOutputStream(authRecordFilePath))
}
}
if (credential.getTokenSync(context) == null) {
error("Not signed in!")
} else {
println("Signed in!")
}
}
Changes
TokenRequestContext().addScopes("https://graph.microsoft.com/.default")
- I'm unsure what scope(s) to add but it seems .default
should be a good start at least. See also https://learn.microsoft.com/en-us/entra/identity-platform/scopes-oidc#the-default-scopecredential.authenticate(context)
in a authenticationRecord
null check (this wasn't obvious to me at first but turned out to be vital)getTokenSync
failsOutput:
Run 1
authenticationRecord is null: true
To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code REDACTED to authenticate.
[ForkJoinPool.commonPool-worker-1] ERROR com.microsoft.aad.msal4jextensions.CrossProcessCacheFileLock - null
Signed in!
Run 2
authenticationRecord is null: false
[ForkJoinPool.commonPool-worker-1] ERROR com.microsoft.aad.msal4jextensions.CrossProcessCacheFileLock - null
Signed in!
@billwert As you can see, it seems I am now running into #28186 - any advice?
@billwert just want to make sure you didn't miss the remaining issue now that the primary issue is resolved (and this issue could suddenly be locked).
Describe the bug I'm following the documentation on token caching, specifically the section on persistent token caching. However, despite the presence of
tokencache.bin
, I'm still asked for credentials on every run.Exception or Stack Trace N/A (though I've run into #28186 when attempting to use the
DeviceCodeCredential
in aSecretClient
after the attached code. As there is no solution proposed for #28186, I'm not sure how to fix that (but let's address my current issue first)).To Reproduce
Run the attached code snippet twice.
Code Snippet
This is my setup based on the documentation. I have inlined the relevant code sample from the documentation above the actual code (representing my understanding of the samples and how I've translated it to kotlin). Note that I'm using blocking functions rather than async intentionally. While the docs use an
InteractiveBrowserCredential
,DeviceCodeCredential
is listed as supported under "Credentials supporting token caching".
```kotlin package com.example import com.azure.identity.* import java.io.FileInputStream import java.io.FileOutputStream fun main() { // String authRecordFilePath = "./tokencache.bin"; val authRecordFilePath = "./tokencache.bin" /* AuthenticationRecord authenticationRecord = AuthenticationRecord.deserialize(new FileInputStream(authRecordFilePath)); InteractiveBrowserCredential interactiveBrowserCredential = new InteractiveBrowserCredentialBuilder() .tokenCachePersistenceOptions(new TokenCachePersistenceOptions()) .authenticationRecord(authenticationRecord) .build(); */ val authenticationRecord: AuthenticationRecord? = runCatching { AuthenticationRecord.deserialize(FileInputStream(authRecordFilePath)) }.getOrNull() println("authenticationRecord is null: ${authenticationRecord == null}") val credential: DeviceCodeCredential = DeviceCodeCredentialBuilder() .tokenCachePersistenceOptions(TokenCachePersistenceOptions()) .authenticationRecord(authenticationRecord) .challengeConsumer { println(it.message) } .build() /* interactiveBrowserCredential.authenticate() .flatMap(authenticationRecord -> { try { return authenticationRecord.serializeAsync(new FileOutputStream(authRecordFilePath)); } catch (FileNotFoundException e) { return Mono.error(e); } }).subscribe(); */ credential.authenticate().block()?.let { authenticationRecord: AuthenticationRecord -> authenticationRecord.serialize(FileOutputStream(authRecordFilePath)) } println("Signed in!") } ```Main.kt
Expected behavior Run 1
Run 2
Actual behavior Run 1
Run 2
Setup (please complete the following information):
com.azure:azure-sdk-bom:1.2.25
com.azure:azure-identity
java { toolchain { languageVersion = 8 } }
but I think my IDE is using 21 during development (I have the issue both in dev and prod). The logs say 21)Additional context
DEBUG level logs for a second run
``` [main] DEBUG reactor.util.Loggers - Using Slf4j logging framework authenticationRecord is null: false [main] DEBUG com.azure.core.implementation.ReflectionUtils - Attempting to use java.lang.invoke package to handle reflection. [main] DEBUG com.azure.core.implementation.ReflectionUtils - Successfully used java.lang.invoke package to handle reflection. [main] DEBUG com.azure.core.implementation.util.Providers - Using com.azure.core.http.netty.NettyAsyncHttpClientProvider as the default com.azure.core.http.HttpClientProvider. [main] DEBUG io.netty.util.internal.logging.InternalLoggerFactory - Using SLF4J as the default logging framework [main] DEBUG io.netty.util.internal.PlatformDependent0 - -Dio.netty.noUnsafe: false [main] DEBUG io.netty.util.internal.PlatformDependent0 - Java version: 21 [main] DEBUG io.netty.util.internal.PlatformDependent0 - sun.misc.Unsafe.theUnsafe: available [main] DEBUG io.netty.util.internal.PlatformDependent0 - sun.misc.Unsafe.copyMemory: available [main] DEBUG io.netty.util.internal.PlatformDependent0 - sun.misc.Unsafe.storeFence: available [main] DEBUG io.netty.util.internal.PlatformDependent0 - java.nio.Buffer.address: available [main] DEBUG io.netty.util.internal.PlatformDependent0 - direct buffer constructor: unavailable: Reflective setAccessible(true) disabled [main] DEBUG io.netty.util.internal.PlatformDependent0 - java.nio.Bits.unaligned: available, true [main] DEBUG io.netty.util.internal.PlatformDependent0 - jdk.internal.misc.Unsafe.allocateUninitializedArray(int): unavailable: class io.netty.util.internal.PlatformDependent0$7 cannot access class jdk.internal.misc.Unsafe (in module java.base) because module java.base does not export jdk.internal.misc to unnamed module @35047d03 [main] DEBUG io.netty.util.internal.PlatformDependent0 - java.nio.DirectByteBuffer.Information Checklist Kindly make sure that you have added all the following information above and checkoff the required fields otherwise we will treat the issuer as an incomplete report