AzureAD / microsoft-authentication-library-for-java

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

[Bug] PublicClientApplication acts like confidential client application #818

Open ShmuelCammebys opened 6 months ago

ShmuelCammebys commented 6 months ago

Library version used

1.14.3

Java version

17

Scenario

PublicClient (AcquireTokenInteractive, AcquireTokenByUsernamePassword)

Is this a new or an existing app?

This is a new app or experiment

Issue description and reproduction steps

Even though my application is a public client application, and uses MSAL for iOS and Android perfectly fine, when I try connecting to the same client id on Desktop (after registering the application for desktop), it says I need a client_secret:

com.microsoft.aad.msal4j.MsalServiceException: AADSTS7000218: The request body must contain the following parameter: 'client_assertion' or 'client_secret'. Trace ID: f8ca8882-0b92-4e97-9fac-bc491d333900 Correlation ID: f43ae1e8-856f-4ef3-a259-fa58a651ab2c Timestamp: 2024-05-08 10:58:05Z

Relevant code snippets

val application = PublicClientApplication
                    .builder(BuildConfig.ENTRA_CLIENT_ID)
                    .authority(BuildConfig.ENTRA_AUTHORITY)
                    .build()
                val prefs = Preferences.userRoot()

                val storedAccountId = prefs.get(Constants.ENTRA_ACCOUNT_IDENTIFIER, "")
                val redirectUri = "http://localhost:55259"
                val scopes = setOf("User.Read")

                val authResult = application.run {
                    if (storedAccountId.isNullOrBlank()) {
                        acquireToken(
                            InteractiveRequestParameters
                                .builder(URI(redirectUri))
                                .scopes(scopes)
                                .build()
                        )
                    } else {
                        acquireTokenSilently(
                            SilentParameters
                                .builder(
                                    scopes,
                                    application
                                        .accounts
                                        .join()
                                        .find { it.tenantProfiles[storedAccountId] != null }
                                )
                                .build()
                        )
                    }
                }
                entraIdToken = authResult.join().idToken()

Expected behavior

Returns auth token

Identity provider

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

Regression

No response

Solution and workarounds

No response

Avery-Dunn commented 6 months ago

Hello @ShmuelCammebys : Just some clarifications:

That 'AADSTS' is an error message coming directly from the token service rather than from our library, so my first guess is a configuration issue in Azure. If you ran your code immediately after configuring the app then maybe the config was slow to propagate? You can also check the 'Manifest' tab to see if the 'allowPublicClient' field is set to true.

ShmuelCammebys commented 6 months ago

@Avery-Dunn

  1. Correct
  2. Don't public client flows have to be allowed for MSAL iOS and Android to work? I mean that the localhost redirect URI was added under the desktop platform.
  3. Interactive.

This has been happening for months after we changed the configuration.

I'd prefer to take this as far as I can before having to contact the security/DevOps team about the Azure config, but if more info is needed, so be it.

Avery-Dunn commented 6 months ago

Don't public client flows have to be allowed for MSAL iOS and Android to work? I mean that the localhost redirect URI was added under the desktop platform.

I'm not too familiar with the implementations for MSAL iOS and Android, but historically the MSALs for mobile scenarios work a bit differently so that setting might not be required for them.

Just looking at one of Android's samples and one for macOS I noticed that the instructions don't mention that "Allow public client flows" setting.

Since there is different platform config options for "Mobile and desktop applications" (iOS/Android) and "Web applications" (more common for Java, .NET, Python, etc.) at least some config in Azure is difference for these scenarios. I can't find good documentation covering exactly when that public client flows setting should be used, but just from experience my understanding was that it's needed for any public client scenario so it may be the missing piece here. Can you confirm whether or not it's enabled for your app?

rayluo commented 6 months ago
  • And "registering the application for desktop" means that you configured the app in Azure to allow public client flows? (Authentication tab -> advanced settings -> 'Allow public client flows')

Don't public client flows have to be allowed for MSAL iOS and Android to work?

Not really. That "allow public client flows" was historically used to enable username password flow and/or device code flow for confidential clients. Mobile platforms like iOS and Android do not require this setting to work.

I mean that the localhost redirect URI was added under the desktop platform.

@ShmuelCammebys , do you have a redirect URI http://localhost, with or without different port, registered as a Web platform in your app's registration? If so, that would cause this symptom. Either remove it from Web platform, or use a different redirect URI with a different /path. Port alone can't be used to differentiate multiple localhost URIs.

ShmuelCammebys commented 6 months ago

This is the config: image

rayluo commented 6 months ago

@ShmuelCammebys , no, I mean this setting.

Configure platforms

There shouldn't be a web platform with http://localhost:12345 (whatever port number it might be) registered for your app. Can you double check to confirm or rule out this possibility?

Avery-Dunn commented 6 months ago

@ShmuelCammebys It looks like I got some public client flows mixed up, and interactive flow is one where you don't need that 'allow public client' config, and like @rayluo said having a "Web application" platform is also incorrect.

I just did some testing to confirm, this is the config that worked for me:

image

ShmuelCammebys commented 6 months ago

@rayluo Yes, there was a SPA redirect URI to localhost. However, when the redirect URI for desktop was set to http://localhost:55259/desktop, MSAL4J hangs at CompletableFuture.join and returns "No Authorization code was returned from the server" (Caused by: java.util.concurrent.CompletionException: com.microsoft.aad.msal4j.MsalClientException: No Authorization code was returned from the server). The browser opens a window to that URL, but MSAL4J never receives the response.

rayluo commented 6 months ago

Yes, there was a SPA redirect URI to localhost.

I haven't personally tried it, but I suppose SPA does not require a redirect URI in the Web platform either. If there is no other usage for a Web platform, @ShmuelCammebys, can you simply delete the http://localhost:whateverport from your Web platform setting, and try again?

However, when the redirect URI for desktop was set to http://localhost:55259/desktop, MSAL4J hangs ...

That might be a different issue. Not all the MSALs currently support a path /path in their interactive flow. Not sure whether that is also applicable to MSAL Java. I'll leave that for @Avery-Dunn to comment. Regardless, I still think there is a good chance that (1) using a path-less localhost redirect URI in your public client; plus (2) removing http://localhost:whateverport from your Web platform setting, shall work.

Avery-Dunn commented 6 months ago

However, when the redirect URI for desktop was set to http://localhost:55259/desktop, MSAL4J hangs ...

That might be a different issue. Not all the MSALs currently support a path /path in their interactive flow. Not sure whether that is also applicable to MSAL Java.

I'm realizing now that our docs don't explicitly say a path will cause issues, but yes only http://localhost or http://localhost:port should be used: https://learn.microsoft.com/en-us/entra/msal/java/getting-started/acquiring-tokens-interactively

You may be able to use SystemBrowserOptions or a custom HTTPClient to handle a redirect with a path, but that's probably more complicated than it's worth.

ShmuelCammebys commented 6 months ago

@Avery-Dunn @rayluo Unfortunately, a localhost redirect URI seems to be required in both platforms. Local web development requires a redirect URI, and our local desktop app development also requires a redirect uri. How can we accomodate both?

rayluo commented 6 months ago

@Avery-Dunn @rayluo Unfortunately, a localhost redirect URI seems to be required in both platforms. Local web development requires a redirect URI, and our local desktop app development also requires a redirect uri. How can we accomodate both?

Generally, a web app is more flexible in terms of having their redirect URI - and its path - configurable; besides, web app typically uses a path in their redirect URI (such as http://localhost/auth). So, a usual workaround is to have your web app to use a redirect URI with path, thus save the path-less redirect URI http://localhost for your desktop app. That way, you can accommodate both, @ShmuelCammebys

You may be able to use SystemBrowserOptions or a custom HTTPClient to handle a redirect with a path, but that's probably more complicated than it's worth.

Indeed. There is a better approach. FWIW, there is a pending improvement for MSAL Python to support customizable path in Desktop's interactive auth, but that PR has been postponed indefinitely.