Oktatóanyag: Jelentkezzen be a felhasználói fiókokba, és használja a Microsoft Graph API-t egy asztali Electron-alkalmazásban

Ebben az oktatóanyagban egy Electron asztali alkalmazást hoz létre, amely bejelentkezteti a felhasználókat, és a PKCE-vel ellátott engedélyezési kódfolyamatot használva hívja meg a Microsoft Graphot. Az asztali alkalmazás, amelyet építesz, a Microsoft Authentication Library-t (MSAL) használja Node.js-hez.

A következőkre vonatkozik: Zöld kör fehér pipa szimbólummal, amely a következő tartalmat jelzi a munkaerő-bérlőkre. Munkaerő-bérlők (további információ)

Ebben az útmutatóban Ön:

  • Az alkalmazás regisztrálása az Azure Portalon
  • Electron asztali alkalmazásprojekt létrehozása
  • Hitelesítési logika hozzáadása az alkalmazáshoz
  • Metódus hozzáadása webes API meghívásához
  • Alkalmazásregisztráció részleteinek hozzáadása
  • Az alkalmazás tesztelése

Előfeltételek

A projekt létrehozása

Megjegyzés:

Az oktatóanyagban szereplő elektronminta kifejezetten az MSAL-csomóponttal való együttműködésre lett kialakítva. Az MSAL-böngésző nem támogatott az Electron-alkalmazásokban. Győződjön meg arról, hogy a projekt helyes beállításához hajtsa végre az alábbi lépéseket.

Hozzon létre egy mappát az alkalmazás üzemeltetéséhez, például az ElectronDesktopApphoz.

  1. Először váltson a projektkönyvtárra a terminálban, majd futtassa a következő npm parancsokat:

    npm init -y
    npm install --save @azure/msal-node @microsoft/microsoft-graph-client isomorphic-fetch bootstrap jquery popper.js
    npm install --save-dev electron@20.0.0
    
  2. Ezután hozzon létre egy App nevű mappát. Ebben a mappában hozzon létre egy index.html nevű fájlt, amely felhasználói felületként szolgál. Adja hozzá a következő kódot:

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
        <meta http-equiv="Content-Security-Policy" content="script-src 'self'" />
        <title>MSAL Node Electron Sample App</title>
    
        <!-- adding Bootstrap 4 for UI components  -->
        <link rel="stylesheet" href="../node_modules/bootstrap/dist/css/bootstrap.min.css">
    </head>
    
    <body>
        <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
            <a class="navbar-brand">Microsoft identity platform</a>
            <div class="btn-group ml-auto dropleft">
                <button type="button" id="signIn" class="btn btn-secondary" aria-expanded="false">
                    Sign in
                </button>
                <button type="button" id="signOut" class="btn btn-success" hidden aria-expanded="false">
                    Sign out
                </button>
            </div>
        </nav>
        <br>
        <h5 class="card-header text-center">Electron sample app calling MS Graph API using MSAL Node</h5>
        <br>
        <div class="row" style="margin:auto">
            <div id="cardDiv" class="col-md-6" style="display:none; margin:auto">
                <div class="card text-center">
                    <div class="card-body">
                        <h5 class="card-title" id="WelcomeMessage">Please sign-in to see your profile and read your mails
                        </h5>
                        <div id="profileDiv"></div>
                        <br>
                        <br>
                        <button class="btn btn-primary" id="seeProfile">See Profile</button>
                    </div>
                </div>
            </div>
        </div>
    
        <!-- importing bootstrap.js and supporting js libraries -->
        <script src="../node_modules/jquery/dist/jquery.js"></script>
        <script src="../node_modules/popper.js/dist/umd/popper.js"></script>
        <script src="../node_modules/bootstrap/dist/js/bootstrap.js"></script>
    
        <!-- importing app scripts | load order is important -->
        <script src="./renderer.js"></script>
    
    </body>
    
    </html>
    
  3. Ezután hozzon létre main.js nevű fájlt, és adja hozzá a következő kódot:

    /*
     * Copyright (c) Microsoft Corporation. All rights reserved.
     * Licensed under the MIT License.
     */
    
    const path = require("path");
    const { app, ipcMain, BrowserWindow } = require("electron");
    
    const AuthProvider = require("./AuthProvider");
    const { IPC_MESSAGES } = require("./constants");
    const { protectedResources, msalConfig } = require("./authConfig");
    const getGraphClient = require("./graph");
    
    let authProvider;
    let mainWindow;
    
    function createWindow() {
        mainWindow = new BrowserWindow({
            width: 800,
            height: 600,
            webPreferences: { preload: path.join(__dirname, "preload.js") },
        });
    
        authProvider = new AuthProvider(msalConfig);
    }
    
    app.on("ready", () => {
        createWindow();
        mainWindow.loadFile(path.join(__dirname, "./index.html"));
    });
    
    app.on("window-all-closed", () => {
        app.quit();
    });
    
    app.on('activate', () => {
        // On OS X it's common to re-create a window in the app when the
        // dock icon is clicked and there are no other windows open.
        if (BrowserWindow.getAllWindows().length === 0) {
            createWindow();
        }
    });
    
    
    // Event handlers
    ipcMain.on(IPC_MESSAGES.LOGIN, async () => {
        const account = await authProvider.login();
    
        await mainWindow.loadFile(path.join(__dirname, "./index.html"));
        
        mainWindow.webContents.send(IPC_MESSAGES.SHOW_WELCOME_MESSAGE, account);
    });
    
    ipcMain.on(IPC_MESSAGES.LOGOUT, async () => {
        await authProvider.logout();
    
        await mainWindow.loadFile(path.join(__dirname, "./index.html"));
    });
    
    ipcMain.on(IPC_MESSAGES.GET_PROFILE, async () => {
        const tokenRequest = {
            scopes: protectedResources.graphMe.scopes
        };
    
        const tokenResponse = await authProvider.getToken(tokenRequest);
        const account = authProvider.account;
    
        await mainWindow.loadFile(path.join(__dirname, "./index.html"));
    
        const graphResponse = await getGraphClient(tokenResponse.accessToken)
            .api(protectedResources.graphMe.endpoint).get();
    
        mainWindow.webContents.send(IPC_MESSAGES.SHOW_WELCOME_MESSAGE, account);
        mainWindow.webContents.send(IPC_MESSAGES.SET_PROFILE, graphResponse);
    });
    

A fenti kódrészletben inicializálunk egy Elektron főablak objektumot, és létrehozunk néhány eseménykezelőt az Elektron ablakkal való interakcióhoz. Emellett importáljuk a konfigurációs paramétereket, példányosítjuk az authProvider osztályt a bejelentkezés, a kijelentkezés és a jogkivonatok beszerzésének kezeléséhez, és meghívjuk a Microsoft Graph API-t.

  1. Ugyanabban a mappában (appban) hozzon létre egy másik fájlt renderer.js, és adja hozzá a következő kódot:

    // Copyright (c) Microsoft Corporation. All rights reserved.
    // Licensed under the MIT License
    
    /**
     * The renderer API is exposed by the preload script found in the preload.ts
     * file in order to give the renderer access to the Node API in a secure and 
     * controlled way
     */
    const welcomeDiv = document.getElementById('WelcomeMessage');
    const signInButton = document.getElementById('signIn');
    const signOutButton = document.getElementById('signOut');
    const seeProfileButton = document.getElementById('seeProfile');
    const cardDiv = document.getElementById('cardDiv');
    const profileDiv = document.getElementById('profileDiv');
    
    window.renderer.showWelcomeMessage((event, account) => {
        if (!account) return;
    
        cardDiv.style.display = 'initial';
        welcomeDiv.innerHTML = `Welcome ${account.name}`;
        signInButton.hidden = true;
        signOutButton.hidden = false;
    });
    
    window.renderer.handleProfileData((event, graphResponse) => {
        if (!graphResponse) return;
    
        console.log(`Graph API responded at: ${new Date().toString()}`);
        setProfile(graphResponse);
    });
    
    // UI event handlers
    signInButton.addEventListener('click', () => {
        window.renderer.sendLoginMessage();
    });
    
    signOutButton.addEventListener('click', () => {
        window.renderer.sendSignoutMessage();
    });
    
    seeProfileButton.addEventListener('click', () => {
        window.renderer.sendSeeProfileMessage();
    });
    
    const setProfile = (data) => {
        if (!data) return;
        
        profileDiv.innerHTML = '';
    
        const title = document.createElement('p');
        const email = document.createElement('p');
        const phone = document.createElement('p');
        const address = document.createElement('p');
    
        title.innerHTML = '<strong>Title: </strong>' + data.jobTitle;
        email.innerHTML = '<strong>Mail: </strong>' + data.mail;
        phone.innerHTML = '<strong>Phone: </strong>' + data.businessPhones[0];
        address.innerHTML = '<strong>Location: </strong>' + data.officeLocation;
    
        profileDiv.appendChild(title);
        profileDiv.appendChild(email);
        profileDiv.appendChild(phone);
        profileDiv.appendChild(address);
    }
    

A renderelő metódusokat úgy teszi elérhetővé az előretöltési szkript, amely a preload.js fájlban található, hogy a renderelő biztonságos és szabályozott módon férhessen hozzá a Node API.

  1. Ezután hozzon létre egy új fájlt preload.js , és adja hozzá a következő kódot:

    // Copyright (c) Microsoft Corporation. All rights reserved.
    // Licensed under the MIT License
    
    const { contextBridge, ipcRenderer } = require('electron');
    
    /**
     * This preload script exposes a "renderer" API to give
     * the Renderer process controlled access to some Node APIs
     * by leveraging IPC channels that have been configured for
     * communication between the Main and Renderer processes.
     */
    contextBridge.exposeInMainWorld('renderer', {
        sendLoginMessage: () => {
            ipcRenderer.send('LOGIN');
        },
        sendSignoutMessage: () => {
            ipcRenderer.send('LOGOUT');
        },
        sendSeeProfileMessage: () => {
            ipcRenderer.send('GET_PROFILE');
        },
        handleProfileData: (func) => {
            ipcRenderer.on('SET_PROFILE', (event, ...args) => func(event, ...args));
        },
        showWelcomeMessage: (func) => {
            ipcRenderer.on('SHOW_WELCOME_MESSAGE', (event, ...args) => func(event, ...args));
        },
    });
    

Ez az előre betöltési szkript egy renderelő API-t tesz elérhetővé, amely lehetővé teszi, hogy a renderelő folyamat által szabályozott hozzáférést biztosítson néhányhoz Node APIs a fő és a renderelő folyamatok közötti kommunikációhoz konfigurált IPC-csatornák alkalmazásával.

  1. Végül hozzon létre egy constants.js nevű fájlt, amely az alkalmazásesemények leírásához tárolja a sztringállandókat:

    /*
     * Copyright (c) Microsoft Corporation. All rights reserved.
     * Licensed under the MIT License.
     */
    
    const IPC_MESSAGES = {
        SHOW_WELCOME_MESSAGE: 'SHOW_WELCOME_MESSAGE',
        LOGIN: 'LOGIN',
        LOGOUT: 'LOGOUT',
        GET_PROFILE: 'GET_PROFILE',
        SET_PROFILE: 'SET_PROFILE',
    }
    
    module.exports = {
        IPC_MESSAGES: IPC_MESSAGES,
    }
    

Most már rendelkezik egy egyszerű grafikus felhasználói felületsel és interakciókkal az Electron-alkalmazáshoz. Az oktatóanyag többi részének elvégzése után a projekt fájl- és mappaszerkezetének a következőhöz hasonlóan kell kinéznie:

ElectronDesktopApp/
├── App
│   ├── AuthProvider.js
│   ├── constants.js
│   ├── graph.js
│   ├── index.html
|   ├── main.js
|   ├── preload.js
|   ├── renderer.js
│   ├── authConfig.js
├── package.json

Hitelesítési logika hozzáadása az alkalmazáshoz

Az Alkalmazás mappában hozzon létre egy AuthProvider.js nevű fájlt. A AuthProvider.js fájl egy hitelesítési szolgáltatói osztályt tartalmaz, amely mSAL-csomópont használatával kezeli a bejelentkezést, a kijelentkezéseket, a jogkivonatok beszerzését, a fiókválasztást és a kapcsolódó hitelesítési feladatokat. Adja hozzá a következő kódot:

/*
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License.
 */

const { PublicClientApplication, InteractionRequiredAuthError } = require('@azure/msal-node');
const { shell } = require('electron');

class AuthProvider {
    msalConfig
    clientApplication;
    account;
    cache;

    constructor(msalConfig) {
        /**
         * Initialize a public client application. For more information, visit:
         * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/initialize-public-client-application.md
         */
        this.msalConfig = msalConfig;
        this.clientApplication = new PublicClientApplication(this.msalConfig);
        this.cache = this.clientApplication.getTokenCache();
        this.account = null;
    }

    async login() {
        const authResponse = await this.getToken({
            // If there are scopes that you would like users to consent up front, add them below
            // by default, MSAL will add the OIDC scopes to every token request, so we omit those here
            scopes: [],
        });

        return this.handleResponse(authResponse);
    }

    async logout() {
        if (!this.account) return;

        try {
            /**
             * If you would like to end the session with AAD, use the logout endpoint. You'll need to enable
             * the optional token claim 'login_hint' for this to work as expected. For more information, visit:
             * https://learn.microsoft.com/azure/active-directory/develop/v2-protocols-oidc#send-a-sign-out-request
             */
            if (this.account.idTokenClaims.hasOwnProperty('login_hint')) {
                await shell.openExternal(`${this.msalConfig.auth.authority}/oauth2/v2.0/logout?logout_hint=${encodeURIComponent(this.account.idTokenClaims.login_hint)}`);
            }

            await this.cache.removeAccount(this.account);
            this.account = null;
        } catch (error) {
            console.log(error);
        }
    }

    async getToken(tokenRequest) {
        let authResponse;
        const account = this.account || (await this.getAccount());

        if (account) {
            tokenRequest.account = account;
            authResponse = await this.getTokenSilent(tokenRequest);
        } else {
            authResponse = await this.getTokenInteractive(tokenRequest);
        }

        return authResponse || null;
    }

    async getTokenSilent(tokenRequest) {
        try {
            return await this.clientApplication.acquireTokenSilent(tokenRequest);
        } catch (error) {
            if (error instanceof InteractionRequiredAuthError) {
                console.log('Silent token acquisition failed, acquiring token interactive');
                return await this.getTokenInteractive(tokenRequest);
            }

            console.log(error);
        }
    }

    async getTokenInteractive(tokenRequest) {
        try {
            const openBrowser = async (url) => {
                await shell.openExternal(url);
            };

            const authResponse = await this.clientApplication.acquireTokenInteractive({
                ...tokenRequest,
                openBrowser,
                successTemplate: '<h1>Successfully signed in!</h1> <p>You can close this window now.</p>',
                errorTemplate: '<h1>Oops! Something went wrong</h1> <p>Check the console for more information.</p>',
            });

            return authResponse;
        } catch (error) {
            throw error;
        }
    }

    /**
     * Handles the response from a popup or redirect. If response is null, will check if we have any accounts and attempt to sign in.
     * @param response
     */
    async handleResponse(response) {
        if (response !== null) {
            this.account = response.account;
        } else {
            this.account = await this.getAccount();
        }

        return this.account;
    }

    /**
     * Calls getAllAccounts and determines the correct account to sign into, currently defaults to first account found in cache.
     * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-common/docs/Accounts.md
     */
    async getAccount() {
        const currentAccounts = await this.cache.getAllAccounts();

        if (!currentAccounts) {
            console.log('No accounts detected');
            return null;
        }

        if (currentAccounts.length > 1) {
            // Add choose account code here
            console.log('Multiple accounts detected, need to add choose account code.');
            return currentAccounts[0];
        } else if (currentAccounts.length === 1) {
            return currentAccounts[0];
        } else {
            return null;
        }
    }
}

module.exports = AuthProvider;

A fenti kódrészletben először inicializáltuk az MSAL-csomópontot PublicClientApplication egy konfigurációs objektum (msalConfig) átadásával. Ezután közzétettük logina logoutgetToken fő modul (main.js) által meghívandó metódusokat. Az login és getToken használatával az MSAL Node acquireTokenInteractive nyilvános API segítségével szerzünk be azonosító- és hozzáférési jogkivonatokat.

Microsoft Graph SDK hozzáadása

Hozzon létre egy graph.js nevű fájlt. A graph.js fájl a Microsoft Graph SDK-ügyfél egy példányát tartalmazza, amely megkönnyíti az adatok elérését a Microsoft Graph API-n az MSAL Node által beszerzett hozzáférési jogkivonat használatával:

const { Client } = require('@microsoft/microsoft-graph-client');
require('isomorphic-fetch');

/**
 * Creating a Graph client instance via options method. For more information, visit:
 * https://github.com/microsoftgraph/msgraph-sdk-javascript/blob/dev/docs/CreatingClientInstance.md#2-create-with-options
 * @param {String} accessToken
 * @returns
 */
const getGraphClient = (accessToken) => {
    // Initialize Graph client
    const graphClient = Client.init({
        // Use the provided access token to authenticate requests
        authProvider: (done) => {
            done(null, accessToken);
        },
    });

    return graphClient;
};

module.exports = getGraphClient;

Alkalmazásregisztráció részleteinek hozzáadása

Hozzon létre egy környezeti fájlt a jogkivonatok beszerzésekor használt alkalmazásregisztrációs adatok tárolásához. Ehhez hozzon létre egy authConfig.js nevű fájlt a minta gyökérmappájában (ElectronDesktopApp), és adja hozzá a következő kódot:

/*
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License.
 */

const { LogLevel } = require("@azure/msal-node");

/**
 * Configuration object to be passed to MSAL instance on creation.
 * For a full list of MSAL.js configuration parameters, visit:
 * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/configuration.md
 */
const AAD_ENDPOINT_HOST = "Enter_the_Cloud_Instance_Id_Here"; // include the trailing slash

const msalConfig = {
    auth: {
        clientId: "Enter_the_Application_Id_Here",
        authority: `${AAD_ENDPOINT_HOST}Enter_the_Tenant_Info_Here`,
    },
    system: {
        loggerOptions: {
            loggerCallback(loglevel, message, containsPii) {
                console.log(message);
            },
            piiLoggingEnabled: false,
            logLevel: LogLevel.Verbose,
        },
    },
};

/**
 * Add here the endpoints and scopes when obtaining an access token for protected web APIs. For more information, see:
 * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/resources-and-scopes.md
 */
const GRAPH_ENDPOINT_HOST = "Enter_the_Graph_Endpoint_Here"; // include the trailing slash

const protectedResources = {
    graphMe: {
        endpoint: `${GRAPH_ENDPOINT_HOST}v1.0/me`,
        scopes: ["User.Read"],
    }
};


module.exports = {
    msalConfig: msalConfig,
    protectedResources: protectedResources,
};

Töltse ki ezeket a részleteket az Azure alkalmazásregisztrációs portálról beszerzett értékekkel:

  • Enter_the_Tenant_Id_here az alábbiak egyikének kell lennie:
    • Ha az alkalmazás támogatja a szervezeti címtárban lévő fiókokat, cserélje le ezt az értéket a bérlőazonosítóra vagy a bérlő nevére. Például: contoso.microsoft.com.
    • Ha az alkalmazás bármely szervezeti könyvtárban támogatja a fiókokat, cserélje le ezt az értéket a következőreorganizations: .
    • Ha az alkalmazás bármely szervezeti címtárban és személyes Microsoft-fiókban támogatja a fiókokat, cserélje le ezt az értéket a következőre common: .
    • Ha szeretné korlátozni a támogatást kizárólag a személyes Microsoft-fiókokra, cserélje le ezt az értéket ezzel: .
  • Enter_the_Application_Id_Here: A regisztrált alkalmazás (ügyfél) azonosítója .
  • Enter_the_Cloud_Instance_Id_Here: Az az Azure-felhőpéldány, amelyben az alkalmazás regisztrálva van.
    • A fő (vagy globális) Azure-felhőbe írja be a következőt https://login.microsoftonline.com/: .
    • A nemzeti felhők (például Kína) esetében a nemzeti felhőkben megtalálhatja a megfelelő értékeket.
  • Enter_the_Graph_Endpoint_Here a Microsoft Graph API azon példánya, amellyel az alkalmazásnak kommunikálnia kell.
    • A globális Microsoft Graph API-végpont esetében cserélje le a sztring mindkét példányát a következőrehttps://graph.microsoft.com/: .
    • A nemzeti felhőbeli üzembe helyezések végpontjait lásd a Microsoft Graph dokumentációjában található "Nemzeti felhőbeli üzembe helyezések" részben.

Az alkalmazás tesztelése

Befejezte az alkalmazás létrehozását, és készen áll az Electron asztali alkalmazás elindítására és az alkalmazás működésének tesztelésére.

  1. Indítsa el az alkalmazást a következő parancs futtatásával a projektmappában:
electron App/main.js
  1. Az alkalmazás főablakában látnia kell a index.html fájl tartalmát és a Bejelentkezés gombot.

Bejelentkezés tesztelése és kijelentkezés

A index.html fájl betöltése után válassza a Bejelentkezés lehetőséget. A rendszer kéri, hogy jelentkezzen be a Microsoft Identity platformmal.

bejelentkezési kérés

Ha ön hozzájárul a kért engedélyekhez, a webalkalmazások megjelenítik a felhasználónevet, és sikeres bejelentkezést jeleznek:

sikeres bejelentkezés

Webes API-hívás tesztelése

A bejelentkezés után válassza a Profil megtekintése lehetőséget a Microsoft Graph API-nak küldött hívás válaszában visszaadott felhasználói profiladatok megtekintéséhez. A hozzájárulást követően a válaszban visszaadott profiladatokat fogja megtekinteni:

profiladatok a Microsoft Graph-ból

Az alkalmazás működése

Amikor egy felhasználó először választja ki a Bejelentkezés gombot, a acquireTokenInteractive MSAL Node metódusa. Ez a módszer átirányítja a felhasználót a bejelentkezésre a Microsoft Identitásplatform végponttal, ellenőrzi a felhasználó hitelesítő adatait, beolvas egy engedélyezési kódot, majd kicseréli a kódot egy azonosító jogkivonatra, hozzáférési jogkivonatra és frissítési jogkivonatra. Az MSAL Node ezeket a jogkivonatokat is tárolja gyorsítótárban későbbi használatra.

Az azonosító alapvető információkat tartalmaz a felhasználóról, például a megjelenített nevét. A hozzáférési jogkivonat élettartama korlátozott, és 24 óra elteltével lejár. Ha ezeket a jogkivonatokat a védett erőforrás eléréséhez szeretné használni, a háttérkiszolgálónak ellenőriznie kell , hogy a jogkivonatot az alkalmazás érvényes felhasználójának adta-e ki.

Az oktatóanyagban létrehozott asztali alkalmazás REST-hívást indít a Microsoft Graph API-hoz egy hozzáférési jogkivonatot használva a kérésfejlécben (RFC 6750).

A Microsoft Graph API-hoz a user.read hatókörnek be kell olvasnia egy felhasználó profilját. Alapértelmezés szerint ez a hatókör automatikusan hozzáadódik minden olyan alkalmazáshoz, amely regisztrálva van az Azure Portalon. A Microsoft Graph egyéb API-jai és a háttérkiszolgálóhoz tartozó egyéni API-k további hatóköröket igényelhetnek. A Microsoft Graph API-nak például a Mail.Read hatókörre van szüksége a felhasználó e-mailjeinek listázásához.

Hatókörök hozzáadásakor előfordulhat, hogy a felhasználók egy másik hozzájárulás megadását kérik a hozzáadott hatókörökhöz.

Súgó és támogatás

Ha segítségre van szüksége, szeretne jelentést készíteni egy problémáról, vagy szeretne többet megtudni a támogatási lehetőségekről, olvassa el a súgót és a fejlesztők támogatását.

Következő lépések

Ha részletesebben szeretne megismerkedni a Node.js és az Electron asztali alkalmazásfejlesztésével a Microsoft Identitásplatform, tekintse meg többrészes forgatókönyv-sorozatunkat: