Log into Azure Enterprise Application with @azure/msal-node library using Electron

Context

I started with this Electron app provided by Microsoft – https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/samples/msal-node-samples/ElectronSystemBrowserTestApp.

You can read the README of the app for more details.

Overall, the app uses the msal-node library. It initialize a PublicClientApplication with a client id and authority associated with an Azure Enterprise Application. It then calls acquireTokenInteractive with a custom ILoopbackClient to fetch the authz code and subsequently the token. The loopback client server is running on localhost, so that requires the redirect URI on the app to be http://localhost.

Problem
I heard it is not recommended to use http://localhost as a redirect so I am trying to adapt the above approach to use a custom protocol (e.g. msal<appId>://auth) as the redirect URI.

I am running into issues. Specifically, when the flow acquires the authz code and calls my custom protocol, it fails.

Here is the browser output

enter image description here

Current Code
Here is what I have so far. I’d expect the call to my custom protocol to succeed since I have configured a handler for that scheme to resolve the authz code. Any guidance would be greatly appreciated!

// AuthProvider.js
const { PublicClientApplication } = require('@azure/msal-node');
const { shell } = require('electron');
const CustomLoopbackClient = require('./CustomLoopbackClient');

class AuthProvider {
    msalConfig;
    clientApplication;
    account;
    cache;

    constructor(msalConfig) {
        this.msalConfig = msalConfig;
        this.clientApplication = new PublicClientApplication(this.msalConfig);
        this.cache = this.clientApplication.getTokenCache();
        this.account = null;
    }
    
    async login() {
        const authResponse = await this.getTokenInteractive();
        console.log(authResponse);

        return this.handleResponse(authResponse);
    }

    async getTokenInteractive() {
        const customLoopbackClient = new CustomLoopbackClient();
        const openBrowser = async (url) => {
            await shell.openExternal(url);
        };
        const interactiveRequest = {
            scopes: [],
            openBrowser,
            loopbackClient: customLoopbackClient, // overrides default loopback client
        };

        const authResponse = await this.clientApplication.acquireTokenInteractive(interactiveRequest);
        return authResponse;
    }
}

module.exports = AuthProvider;
const { protocol } = require('electron');

class CustomLoopbackClient {
    async listenForAuthCode() {
        const authCodeListener = new Promise(
            (resolve, reject) => {
                protocol.handle("msal<appId>", (req, callback) => {
                    const requestUrl = new URL(req.url);
                    const authCode = requestUrl.searchParams.get("code");
                    if (authCode) {
                        resolve(authCode);
                    } else {
                        protocol.unhandle("msal<appId>");
                        reject(new Error("No code found in URL"));
                    }
                });
            }
        );

        return authCodeListener;
    }

    getRedirectUri() { return "msal<appId>://auth"; }

    closeServer() { protocol.unhandle("msal<appId>"); }
}

module.exports = CustomLoopbackClient;
// main.js
const path = require("path");
const { app, BrowserWindow, ipcMain } = require('electron/main')
const AuthProvider = require("./AuthProvider");

let win;
let authProvider;
const msalConfig = {
    auth: {
        clientId: "<appId>",
        authority: "https://login.microsoftonline.com/<tenantId>",
        redirectUri: "msal<appId>://auth"
    },
    cache: {
        cacheLocation: 'localStorage',
        storeAuthStateInCookie: true
    }
}

const createWindow = () => {
    win = new BrowserWindow({
        title: 'Test App',
        width: 800,
        height: 600,
        webPreferences: {
            contextIsolation: true,
            nodeIntegration: true,
        }
    });

    authProvider = new AuthProvider(msalConfig);

    win.webContents.openDevTools();
    win.loadFile(path.join(__dirname, "./app/build/index.html"));
}

app.whenReady().then(() => {
    createWindow();
});

As a related question, is there an issue using localhost as the redirect URI for a desktop app? Thanks!