AzureAD / microsoft-authentication-library-for-java

Microsoft Authentication Library (MSAL) for Java http://aka.ms/aadv2
MIT License
280 stars 137 forks source link

[Bug] xms_st.sub-Claim missing in access-token prevents calling MS Graph Userinfo #835

Open marbon87 opened 3 days ago

marbon87 commented 3 days ago

Library version used

1.16.0

Java version

21

Scenario

Other - please specify

Is this a new or an existing app?

This is a new app or experiment

Issue description and reproduction steps

We are using keycloak as an internal idp and want to use the external to internal token exchange feature.

Therefor i acquire a token silently with MSAL4j and post the access token to keycloak. The problem is that keycloak call the MS Graph userinfo-Endpoint but get's the error: "Token must contain sub claim."

When i acquire an access token by calling the following uri in the browser and use the access-token from the redirect, the token exchange is working:

https://login.microsoftonline.com/my-tenant/oauth2/v2.0/authorize?client_id=my-client-id&response_type=token+id_token&redirect_uri=https://localhost&scope=user.read+openid+profile+email&response_mode=fragment&state=12345&nonce=678910

I compared the two access tokens from MSAL4j and in the browser and guess that the problem in the MSAL4j-access token is the missing xms_st.sub-Claim in the access token.

What do i have to configure, to get that scope in the access token from MSAL4j?

Relevant code snippets

package org.example;

import com.microsoft.aad.msal4j.IAuthenticationResult;
import com.microsoft.aad.msal4j.MsalException;
import com.microsoft.aad.msal4j.PublicClientApplication;
import com.microsoft.aad.msal4j.SilentParameters;
import com.microsoft.aad.msal4jbrokers.Broker;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Set;

public class Example {

    private static final Set<String> scope = Set.of("user.read", "openid", "profile", "upn", "preferred_username");
    private static final String clientId = "my-client-id";

    public static void main(String args[]) throws Exception {
        Broker broker = new Broker.Builder()
                .supportWindows(true).build();

        PublicClientApplication pca = PublicClientApplication.builder(clientId)
                .broker(broker)
                .build();

        IAuthenticationResult result = acquireTokenIntegratedWindowsAuth(pca, scope);
        System.out.println("Account username:  " + result.account().username());
        System.out.println("Access token:      " + result.accessToken());
        System.out.println("Id token:          " + result.idToken());

        HttpClient client = HttpClient.newHttpClient();

        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://graph.microsoft.com/oidc/userinfo"))
                .header("Authorization", "Bearer " + result.accessToken())
                .build();

        HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

        System.out.println("User-Info Status:              " + response.statusCode());
        System.out.println("User-Info Result:              " + response.body());

    }

    private static IAuthenticationResult acquireTokenIntegratedWindowsAuth(PublicClientApplication pca,
                                                                           Set<String> scopes) throws Exception {

        IAuthenticationResult result;
        try {
            SilentParameters silentParameters =
                    SilentParameters
                            .builder(scopes)
                            .tenant("<my-tenent>")
                            .forceRefresh(true)
                            .scopes(Set.of("https://graph.microsoft.com/.default"))
                            .build();
            result = pca.acquireTokenSilently(silentParameters).join();
        } catch (MsalException ex) {
            throw ex;
        }
        return result;
    }
}

Expected behavior

MS Graph API Userinfo-Endpoint should respond with status code 200.

Identity provider

Microsoft Entra ID (Work and School accounts and Personal Microsoft accounts)

Regression

No response

Solution and workarounds

No response

Avery-Dunn commented 3 days ago

Hello @marbon87 : Just to clarify a couple of things:

After some testing I'm having trouble reproducing your exact issue, my tokens either correctly have that "xms_st" claim or I run into a different issue before getting the token.

However, my initial thought is that by default we will set the authority to https://login.microsoftonline.com/common/, and I noticed that your working URL has https://login.microsoftonline.com/my-tenant/. So, you may need to specify an authority in your PublicClientApplication such as https://login.microsoftonline.com/your-tenant-id/ in order for the access token to be created with the right ID token info.

marbon87 commented 18 hours ago
  • When you say that you can get working tokens in the browser you mean you're getting them from that URL manually, right? Not from MSAL's interactive/browser-based flow?

Correct.

  • For the working tokens, I assume the ID token has a "sub" claim that matches what is in the "xms_st" claim of the access token.

Correct.

  • Is the "sub" in the working ID token the same as the "sub" in ID token you get from MSAL? (and the access token from MSAL is just missing "xms_st")

Correct: Both id tokens have the same sub-claim, starting with -qr.... This value is equal to the xms_st.sub-Claim in the working acsess-token. The sub-claim of the workin access-token matches the sub-claim from MSAL. The MSAL-access-token totally misses the xms_st.sub-Claim.

After some testing I'm having trouble reproducing your exact issue, my tokens either correctly have that "xms_st" claim or I run into a different issue before getting the token.

However, my initial thought is that by default we will set the authority to https://login.microsoftonline.com/common/, and I noticed that your working URL has https://login.microsoftonline.com/my-tenant/. So, you may need to specify an authority in your PublicClientApplication such as https://login.microsoftonline.com/your-tenant-id/ in order for the access token to be created with the right ID token info.

I set the authority but get the same error:

        PublicClientApplication pca = PublicClientApplication.builder(clientId)
                .broker(broker)
                .authority("https://login.microsoftonline.com/tenant-id")
                .build();