Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Bug] Cannot call the OIDC endpoint with access token from the broker #835

Open
marbon87 opened this issue Jul 5, 2024 · 21 comments
Open
Labels
Broker For issues related to the msal4j-brokers package Bug Something isn't working, needs an investigation and a fix P2 Normal priority items, should be done after P1 public-client For questions/issues related to public client apps

Comments

@marbon87
Copy link

marbon87 commented Jul 5, 2024

Library version used

1.16.0

Java version

21

Is this a new or an existing app?

This is a new app or experiment

Summary of the issue

  1. An external component (KeyCloak) integrates with OIDC providers by making a call to the userinfo_endpoint, which it reads from the OIDC endpoint.
  2. I can get a WAM token for Graph, but I cannot call the endpoint (401). I am able to call when using a browser!

OIDC endpoint: https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration
UserInfo endpoint: https://graph.microsoft.com/oidc/userinfo
Scope requested: User.Read

See a simple repo (in C#) in #835 (comment)

Note that I can call https://graph.microsoft.com/v1.0/me endpoint with the WAM token.

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

@marbon87 marbon87 added needs attention Automatically used when an issue is created through an issue template untriaged Automatically used when an issue is created through an issue template labels Jul 5, 2024
@Avery-Dunn
Copy link
Collaborator

Avery-Dunn commented Jul 5, 2024

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

  • 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?
  • 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. 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")

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.

@Avery-Dunn Avery-Dunn added Bug Something isn't working, needs an investigation and a fix Requires more info More information is needed, from either the person who opened the issue or another team and removed needs attention Automatically used when an issue is created through an issue template untriaged Automatically used when an issue is created through an issue template labels Jul 5, 2024
@marbon87
Copy link
Author

marbon87 commented Jul 8, 2024

  • 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();

@bgavrilMS bgavrilMS added P2 Normal priority items, should be done after P1 public-client For questions/issues related to public client apps labels Jul 9, 2024
@bgavrilMS
Copy link
Member

Can you clarify if you are using the Id Token or the access token?

The broker uses Entra's v1 endpoint, and so Id Tokens will be v1, which are somewhat different than the v2 endpoint Id Tokens (which you are using). Access tokens however should be the same, they are determined by the resource (Graph).

@marbon87
Copy link
Author

marbon87 commented Jul 9, 2024

Hi @bgavrilMS ,
i am using only access tokens to call the userinfo endpoint as shown in the example above.

@marbon87
Copy link
Author

marbon87 commented Jul 9, 2024

Is https://graph.microsoft.com/.default the correct URI for the userinfo endpoint?

If i try https://login.microsoftonline.com/tenant-idopenid/userinfo i also get a 400 reponse with WWW-Authenticate-Header:

Bearer correlation_id="..........", error="invalid_request", error_codes="[9001014]", error_description="AADSTS9001014: This token was not issued for the UserInfo endpoint. This may have been a token for Graph or another resource. Trace ID: f7b3c682-7356-4cc7-a42f-3fc9d7bd5e00 Correlation ID: 49f31763-c0ff-4dab-8e88-32dd1725dcfb Timestamp: 2024-07-09 08:16:49Z", error_uri="https://login.microsoftonline.com/error?code=9001014", timestamp="2024-07-09 08:16:49Z", trace_id="f7b3c682-7356-4cc7-a42f-3fc9d7bd5e00"

@marbon87
Copy link
Author

Hi @bgavrilMS , do you have any updates on this?

@bgavrilMS bgavrilMS added External and removed Requires more info More information is needed, from either the person who opened the issue or another team public-client For questions/issues related to public client apps labels Aug 26, 2024
@bgavrilMS
Copy link
Member

The userinfo endpoint is part of the OIDC document, which all IdP are required to publish. You can find Entra's here:

https://login.microsoftonline.com/common/.well-known/openid-configuration

I can imagine that KeyCloak will try to find the userinfo endpoint by looking at the iss claim from the AAD token and adding /.well-known to it.

Let me try this out on a personal tenant...

@bgavrilMS
Copy link
Member

@marbon87 - let me see if I understand correctly: who calls the userinfo endpoint ? KeyCloak or your app?

I wasn't able to get a token for the user endpoint, Entra keeps giving me a token with Graph scopes. And the user endpoint refuses it - with error error_description="AADSTS9001014: This token was not issued for the UserInfo endpoint. This may have been a token for Graph or another resource."

Is this what you are getting? (the error message is part of the 400)

@marbon87
Copy link
Author

marbon87 commented Aug 26, 2024

@marbon87 - let me see if I understand correctly: who calls the userinfo endpoint ? KeyCloak or your app?

Keycloak calls the userinfo enpdoint.

I wasn't able to get a token for the user endpoint, Entra keeps giving me a token with Graph scopes. And the user endpoint refuses it - with error error_description="AADSTS9001014: This token was not issued for the UserInfo endpoint. This may have been a token for Graph or another resource."

Is this what you are getting? (the error message is part of the 400)

Exactly.

@bgavrilMS
Copy link
Member

Ah ok, I think I figured it out. Entra has v1 and v2 endpoints. The OIDC document for v2 is here (notice the v2.0 segment)

https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration

And so the userinfo endpoint is:
image

The userinfo is indeed hosted by Graph, but this is an implementation detail.

The key point here is that when you configure federation between KeyCloak and Entra, you must tell KeyCloak to use the v2 endpoints of AAD. So the authority from KeyCloak's perspective is https://login.microsoftonline.com/common/v2.0/. MSAL libraries "abstract" this away and will add "v2.0" on their own.

HTH

@bgavrilMS bgavrilMS added Question Any questions about how MSAL Java works answered and removed Bug Something isn't working, needs an investigation and a fix P2 Normal priority items, should be done after P1 labels Aug 26, 2024
@marbon87
Copy link
Author

Sorry, but this is also not working.
If i call https://graph.microsoft.com/oidc/userinfo manually (with curl) with the access-token from MSAL4j i get the following error:

{"error":{"code":"UnknownError","message":"Token must contain sub claim.","innerError":{"date":"2024-08-26T12:32:37","request-id":"2047b161-fcf0-4fe1-ad7d-55eb8deafac6"

@bgavrilMS
Copy link
Member

How does your token look like? (make sure to hide any PII like name etc) Here's mine from https://jwt.ms

image

@marbon87
Copy link
Author

marbon87 commented Aug 26, 2024

I sent it to you by email.
Could you share your java code to get the token? Mine is above.

@bgavrilMS
Copy link
Member

bgavrilMS commented Aug 26, 2024

Thanks I got it. I think it's a broker issue.

I am able to get a response if I use the browser to authenticate, but I get the Token must contain sub claim error if I use the broker. Obvs the token contains the sub claim (it's mandatory per OIDC).

Here's my C# code that repros the issue.

internal class Program
{
    private const string ClientId = "3bee2617-ab99-4ba5-b390-be397057344f";
    //private const string TenantId = "839846c0-9cef-4455-9542-0c36d831d026";
    private const string TenantId = "organizations";
    private static readonly Uri AuthorityUri = new Uri($"https://login.microsoftonline.com/{TenantId}");
    private static readonly Uri RedirectUrl = new Uri("http://localhost");
    private static readonly string[] Scopes = new[] { "User.Read" };
    
    private static bool s_useBroker = true;

    private static async Task Main(string[] args)
    {

        [DllImport("user32.dll")]
        static extern nint GetForegroundWindow();

        var brokerOptions = s_useBroker ? 
            new BrokerOptions(BrokerOptions.OperatingSystems.Windows) : 
            new BrokerOptions(BrokerOptions.OperatingSystems.None);

        var pca = PublicClientApplicationBuilder
            .Create(ClientId)
            .WithAuthority(AuthorityUri)
            .WithRedirectUri(RedirectUrl.ToString())
            .WithParentActivityOrWindow(() => GetForegroundWindow())
            .WithBroker(brokerOptions)
            .Build();

        var result = await pca.AcquireTokenInteractive(Scopes)
            .WithPrompt(Prompt.SelectAccount)
            .ExecuteAsync();

        HttpClient client = new HttpClient();
        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(result.TokenType, result.AccessToken);

        var response2 = await client.GetAsync("https://graph.microsoft.com/oidc/userinfo"); // OIDC - fails with broker, works with browser
        var stringResponse2 = await response2.Content.ReadAsStringAsync();
        Console.Write(stringResponse2);

        var response3 = await client.GetAsync("https://graph.microsoft.com/v1.0/me");  // works with broker and with browser
        var stringResponse3 = await response3.Content.ReadAsStringAsync();
        Console.Write(stringResponse3);

    }
}

@bgavrilMS bgavrilMS removed the Question Any questions about how MSAL Java works label Aug 26, 2024
@bgavrilMS bgavrilMS added Bug Something isn't working, needs an investigation and a fix P2 Normal priority items, should be done after P1 public-client For questions/issues related to public client apps Broker For issues related to the msal4j-brokers package and removed External answered labels Aug 26, 2024
@bgavrilMS bgavrilMS changed the title [Bug] xms_st.sub-Claim missing in access-token prevents calling MS Graph Userinfo [Bug] Cannot call the OIDC endpoint with the broker Aug 26, 2024
@marbon87
Copy link
Author

That the token from the browser is working is mentioned in my first post.
Browser is not an option, that's why i wanted to use MSAL4j. Furthermore i do not have any knowledge in .NET.

Do you have a dedicated contact or business support for this kind of issue? We are actually paying a lot for MS Entra...

@bgavrilMS bgavrilMS changed the title [Bug] Cannot call the OIDC endpoint with the broker [Bug] Cannot call the OIDC endpoint with access token from the broker Aug 26, 2024
@bgavrilMS
Copy link
Member

Yes, please do escalate this issue via Azure support and via your account manager to get more attention to it.

CC @localden and @ashok672

@bgavrilMS
Copy link
Member

I'm editing the issue to make it clear, as it's a pretty long thread.

@bgavrilMS
Copy link
Member

@marbon87 - can you convince KeyCloack to call https://graph.microsoft.com/v1.0/me instead of the userinfo endpoint? I think they are similar.

@marbon87
Copy link
Author

marbon87 commented Sep 9, 2024

https://graph.microsoft.com/v1.0/me does not work either because the response is missing, especiall the sub-claim is missing in the response.

@iulico-1
Copy link

This is a known, recently discovered defect, in eSTS implementation of the protocol used by Windows broker to issue v2 tokens.

We don't have a concrete timeline, but the plan is for eSTS to address this next quarter.

@marbon87
Copy link
Author

marbon87 commented Nov 5, 2024

https://graph.microsoft.com/v1.0/me seems to be working now. Is this uri expected to be used for userinfo?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Broker For issues related to the msal4j-brokers package Bug Something isn't working, needs an investigation and a fix P2 Normal priority items, should be done after P1 public-client For questions/issues related to public client apps
Projects
None yet
Development

No branches or pull requests

4 participants