2

We are using Microsoft graph sdk for java for the beta api version 0.7.0: https://github.com/microsoftgraph/msgraph-beta-sdk-java

I go through the authentication code flow and in the code that receives the authentication code, we have code like they have in the example here: https://learn.microsoft.com/en-us/graph/sdks/choose-authentication-providers?tabs=Java

final AuthorizationCodeCredential authCodeCredential = new AuthorizationCodeCredentialBuilder()
        .clientId(clientId)
        .clientSecret(clientSecret) //required for web apps, do not set for native apps
        .authorizationCode(authorizationCode)
        .redirectUrl(redirectUri)
        .build();

final TokenCredentialAuthProvider tokenCredentialAuthProvider = new TokenCredentialAuthProvider(scopes, authCodeCredential);

final GraphServiceClient graphClient =
  GraphServiceClient
    .builder()
    .authenticationProvider(tokenCredentialAuthProvider)
    .buildClient();

final User me = graphClient.me().buildRequest().get();

This first call using the graphClient works but subsequent call fails. We steps through some of the classes and in the AuthorizationCodeCredential code to get token:

    @Override
    public Mono<AccessToken> getToken(TokenRequestContext request) {
        return Mono.defer(() -> {
            if (cachedToken.get() != null) {
                return identityClient.authenticateWithPublicClientCache(request, cachedToken.get())
                    .onErrorResume(t -> Mono.empty());
            } else {
                return Mono.empty();
            }
        }).switchIfEmpty(
            Mono.defer(() -> identityClient.authenticateWithAuthorizationCode(request, authCode, redirectUri)))
               .map(msalToken -> {
                   cachedToken.set(new MsalAuthenticationAccount(
                                new AuthenticationRecord(msalToken.getAuthenticationResult(),
                                        identityClient.getTenantId(), identityClient.getClientId())));
                   return (AccessToken) msalToken;
               })
            .doOnNext(token -> LoggingUtil.logTokenSuccess(logger, request))
            .doOnError(error -> LoggingUtil.logTokenError(logger, request, error));
    }

On the second call using the client there is a cached token but the call to identityClient.authenticateWithPublicClientCache fails with: MsalClientException: Token not found in the cache

Then it falls into the empty case and tries to use the authentication code again which fails because it's already been used from the first call.

We tried setting the tenandId in the calls in case that would help but it did not.

If anyone has some insight into what we need to do to make this work. Please let me know.

Regards, LT

2 Answers 2

0

There are two flows, however, in which you should not attempt to silently acquire a token:

https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-acquire-cache-tokens#recommended-call-pattern-for-public-client-applications

  • Client credentials flow, which does not use the user token cache but an application token cache. This method takes care of verifying the application token cache before sending a request to the security token service (STS).
  • Authorization code flow in web apps, as it redeems a code that the application obtained by signing in the user and having them consent to more scopes. Since a code and not an account is passed as a parameter, the method can't look in the cache before redeeming the code, which invokes a call to the service.

If you want to acquire tokens silently from the cache, please try the code here.

private static IAuthenticationResult acquireTokenInteractive() throws Exception {

    // Load token cache from file and initialize token cache aspect. The token cache will have
    // dummy data, so the acquireTokenSilently call will fail.
    TokenCacheAspect tokenCacheAspect = new TokenCacheAspect("sample_cache.json");

    PublicClientApplication pca = PublicClientApplication.builder(CLIENT_ID)
            .authority(AUTHORITY)
            .setTokenCacheAccessAspect(tokenCacheAspect)
            .build();

    Set<IAccount> accountsInCache = pca.getAccounts().join();
    // Take first account in the cache. In a production application, you would filter
    // accountsInCache to get the right account for the user authenticating.
    IAccount account = accountsInCache.iterator().next();

    IAuthenticationResult result;
    try {
        SilentParameters silentParameters =
                SilentParameters
                        .builder(SCOPE, account)
                        .build();

        // try to acquire token silently. This call will fail since the token cache
        // does not have any data for the user you are trying to acquire a token for
        result = pca.acquireTokenSilently(silentParameters).join();
    } catch (Exception ex) {
        if (ex.getCause() instanceof MsalException) {

            InteractiveRequestParameters parameters = InteractiveRequestParameters
                    .builder(new URI("http://localhost"))
                    .scopes(SCOPE)
                    .build();

            // Try to acquire a token interactively with system browser. If successful, you should see
            // the token and account information printed out to console
            result = pca.acquireToken(parameters).join();
        } else {
            // Handle other exceptions accordingly
            throw ex;
        }
    }
    return result;
}
Sign up to request clarification or add additional context in comments.

1 Comment

I don't think it is the case that we want to silently acquire the token with authentication code flow. As I mentioned, we go through the authentication code flow normally and from stepping through the code AuthorizationCodeCredential class gets both the access token and refresh token. All we really want to do is to continue to use the access token and when needed get new access token using the refresh token. The problem is that the way these classes are implemented, there is no access to these.
0

Use the TokenCredential setup that lets you refresh without reusing the authorization code.

For ongoing sessions, use DeviceCodeCredential or InteractiveBrowserCredential. These can keep a session going through browser login and work better with token cache storage.

Example:

InteractiveBrowserCredential credential = new InteractiveBrowserCredentialBuilder() 
.clientId(clientId)
.redirectUrl(redirectUri).build();

Then use:

GraphServiceClient graphClient = GraphServiceClient.builder()
.authenticationProvider(new TokenCredentialAuthProvider 
(scopes, credential)).buildClient();

This allows multiple requests because InteractiveBrowserCredential handles refreshes internally.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.