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] WAM Broker doesn't start from SWT UI #804

Closed alexanderbecher closed 3 months ago

alexanderbecher commented 3 months ago

Library version used

1.15.0

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

I'm trying to call acquireToken with the broker from a SWT UI. This just locks up the program and never shows a browser window. Same code is working without UI. I thin I've provided the correct window handle but it doesn't work. Cant't figure out where and why it locks up. Log from unsuccessful attempt:

[main] INFO com.microsoft.azure.javamsalruntime.MsalRuntimeInterop - Setting up MSALRuntime. [main] INFO com.microsoft.azure.javamsalruntime.Callbacks - (MSALRuntime log) [MSAL:0001] INFO Startup:53 Starting up MSAL [main] INFO com.microsoft.azure.javamsalruntime.Callbacks - (MSALRuntime log) [MSAL:0001] INFO AddConfigurationWithStorageManager:195 Created a new public client application with ID '' [main] INFO com.microsoft.azure.javamsalruntime.MsalRuntimeInterop - MSALRuntime startup API called successfully. [main] INFO com.microsoft.aad.msal4jbrokers.Broker - MSALRuntime started successfully. MSAL Java will use MSALRuntime in all supported broker flows. [main] INFO com.microsoft.azure.javamsalruntime.Callbacks - (MSALRuntime log) [MSAL:0001] INFO SetCorrelationId:237 Set correlation ID: XXX-8433-4a26-a6b3-XXX [main] INFO com.microsoft.azure.javamsalruntime.Callbacks - (MSALRuntime log) [MSAL:0001] INFO ExecuteInteractiveRequest:958 The original authority is 'https://login.microsoftonline.com/XXX-ab98-4022-a0ff-XXX' [main] INFO com.microsoft.azure.javamsalruntime.Callbacks - (MSALRuntime log) [MSAL:0001] INFO ExecuteInteractiveRequest:969 The normalized realm is '' [main] INFO com.microsoft.azure.javamsalruntime.Callbacks - (MSALRuntime log) [MSAL:0001] INFO ModifyAndValidateAuthParameters:225 Authority Realm: XXX-ab98-4022-a0ff-XXX [main] WARN com.microsoft.azure.javamsalruntime.Callbacks - (MSALRuntime log) [MSAL:0001] WARNING ExecuteInteractiveRequest:1034 Not attempting to register the device. [Thread-11] WARN com.microsoft.azure.javamsalruntime.Callbacks - (MSALRuntime log) [MSAL:0002] WARNING ReadAccountById:225 Account id is empty - account not found

Am I doing something wrong?

Relevant code snippets

import java.io.FileInputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.util.Collections;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

import com.microsoft.aad.msal4j.InteractiveRequestParameters;
import com.microsoft.aad.msal4j.PublicClientApplication;
import com.microsoft.aad.msal4jbrokers.Broker;

public class WamSWTExample {

    public static final URI REDIRECT_URI = URI.create("http://localhost");;
    private static String authority;
    private static String clientId;
    private static String tenantId;
    private static Set<String> scope;

    public static void main(String[] args) {
        WamSWTExample wamSWTExample = new WamSWTExample();
        wamSWTExample.createUI();
        // Commenbt previous line and uncomment following lines to call from console.
        // try {
        // wamSWTExample.authenticate(456789L);
        // } catch (IOException e) {
        // e.printStackTrace();
        // }
    }

    public void createUI() {
        Display display = new Display();
        Shell shell = new Shell(display);
        shell.setText("WAM Interactive Login");
        shell.setSize(400, 100);
        shell.setLayout(new GridLayout(1, false));
        Button button = new Button(shell, SWT.PUSH);
        button.setText("Interactive Login");
        button.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                long handle = shell.handle;
                try {
                    authenticate(handle);
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        });

        shell.open();
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch())
                display.sleep();
        }
        display.dispose();
    }

    public void authenticate(long handle) throws IOException, MalformedURLException {
        initialize();

        Broker wamBroker = new Broker.Builder().supportWindows(true).build();
        wamBroker.enableBrokerLogging(true);
        wamBroker.enableBrokerPIILogging(true);
        PublicClientApplication pca = PublicClientApplication.builder(clientId).broker(wamBroker).authority(authority)
                .build();

        InteractiveRequestParameters interactiveRequestParams = InteractiveRequestParameters.builder(REDIRECT_URI)
                .scopes(scope).windowHandle(handle).instanceAware(true).tenant(tenantId).build();

        pca.acquireToken(interactiveRequestParams).exceptionally(ex -> {
            ex.printStackTrace();
            return null;
        }).thenAcceptAsync(result -> {
            if (result != null) {
                System.out.println("Username: " + result.account().username());
                System.out.println("Access token: " + result.accessToken());
                System.out.println("Id token: " + result.idToken());
            }
        }).join();

    }

    private static void initialize() throws IOException {
        Properties properties = new Properties();
        properties.load(new FileInputStream(
                Objects.requireNonNull(Thread.currentThread().getContextClassLoader().getResource("")).getPath()
                        + "application.properties"));
        clientId = properties.getProperty("CLIENT_ID");
        tenantId = properties.getProperty("TENANT_ID");
        scope = Collections.singleton(properties.getProperty("SCOPE"));
        authority = properties.getProperty("AUTHORITY");
    }
}

/** pom.xml:

 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com</groupId>
  <artifactId>SWTBrokerExample</artifactId>
  <version>0.0.1-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>com.microsoft.azure</groupId>
            <artifactId>msal4j</artifactId>
            <version>1.15.0</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.29</version>
        </dependency>
        <dependency>
            <groupId>org.json</groupId>
            <artifactId>json</artifactId>
            <version>20090211</version>
        </dependency>
        <dependency>
            <groupId>com.microsoft.azure</groupId>
            <artifactId>msal4j-brokers</artifactId>
            <version>1.0.3-beta</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.platform</groupId>
            <artifactId>org.eclipse.swt</artifactId>
            <version>3.125.0</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.platform</groupId>
            <artifactId>org.eclipse.swt.win32.win32.x86_64</artifactId>
            <version>3.125.0</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>17</source>
                    <target>17</target>
                </configuration>
            </plugin>
        </plugins>
    </build>    
</project>

**/

Expected behavior

No response

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 months ago

Hello @alexanderbecher : To clarify, when you say it's "working without UI", you're still using the broker but just with a silent, non-UI flow, right? And the program just locks up as though it's waiting for you to interact with a pop-up window?

When using the WAM broker, what should happen is that a login window pops up asking for the user's credentials. It isn't a browser window (like the normal non-broker interactive flow), and it may be popping up behind something else or on a different screen if you have multiple monitors.

The code looks correct, and since the non-UI flow is working my best guess is that a different window handle is needed. I'm not too familiar with the SWT API, but there may be either a parent or child window for the one you're using that would give better behavior.

bgavrilMS commented 3 months ago

You can always try to provide a different window handle like:

https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdesktopwindow or https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getforegroundwindow

to see if the pop-up appears (just for debugging)

alexanderbecher commented 3 months ago

Hi @Avery-Dunn , yes I meant non Ui with broker. @bgavrilMS I've tried it with

HWND foregroundWindow = com.sun.jna.platform.win32.User32.INSTANCE.GetDesktopWindow();
long nativeValue = com.sun.jna.Pointer.nativeValue(foregroundWindow.getPointer());

this doesn't work from my SWT example, it still locks up and show no browser window. However when I try this with a Swing example it opens the browser window:

import java.io.IOException;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

import com.sun.jna.platform.win32.WinDef.HWND;

public class SwingExample {
    public static void main(String[] args) {
        JFrame frame = new JFrame("TestWindow");
        frame.setVisible(true);
        frame.setSize(500, 200);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JPanel panel = new JPanel();
        frame.add(panel);
        JButton button = new JButton("Interactive auth");
        panel.add(button);

        button.addActionListener(e -> {
            System.out.println("test");
            HWND foregroundWindow = com.sun.jna.platform.win32.User32.INSTANCE.GetDesktopWindow();

            long nativeValue = com.sun.jna.Pointer.nativeValue(foregroundWindow.getPointer());
            WamSWTExample wamSWTExample = new WamSWTExample();
            try {
                wamSWTExample.authenticate(nativeValue);
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        });
    }
}
bgavrilMS commented 3 months ago

Workaround is to use system browser instead of broker.

alexanderbecher commented 3 months ago

Ok, I've managed to get it running. You just need another thread that's not the UIThread:

new Thread(() -> {
    try {
        authenticate(handle);
    } catch (IOException e1) {
        e1.printStackTrace();
    }
}).start();