Oktatóanyag: Jelentkezzen be a felhasználókba, és hívja meg a Microsoft Graph API-t egy asztali Electron-alkalmazásban

Ebben az oktatóanyagban létrehoz egy Electron asztali alkalmazást, amely bejelentkezik a felhasználókba, és meghívja a Microsoft Graphot a PKCE engedélyezési kódfolyamatával. A buildelt asztali alkalmazás a Node.js Microsoft Authentication Library -t (MSAL) használja.

Kövesse az oktatóanyag lépéseit a következőkhöz:

  • 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

Az alkalmazás regisztrálása

Először végezze el az alkalmazás regisztrálásához szükséges lépéseket az alkalmazás regisztrálásához az Microsoft Identitásplatform.

Az alkalmazásregisztrációhoz használja az alábbi beállításokat:

  • Név: ElectronDesktopApp (javasolt)
  • Támogatott fióktípusok: Csak a szervezeti címtárban lévő fiókok (egyetlen bérlő)
  • Platform típusa: Mobil- és asztali alkalmazások
  • Átirányítási URI: http://localhost

A projekt létrehozása

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 egy 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 (App) hozzon létre egy másik fájlt renderer.js néven, é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 a preload.js fájlban található preload szkript teszi elérhetővé annak érdekében, hogy a renderelő biztonságos és szabályozott módon férhessen Node API hozzá

  1. Ezután hozzon létre egy új fájlt preload.js fájlként , é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. Az AuthProvider.js fájl egy hitelesítési szolgáltatói osztályt tartalmaz, amely az MSAL-csomópont használatával kezeli a bejelentkezési, kijelentkezési, jogkivonat-beszerzési, fiókválasztási és 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, az MSAL Node acquireTokenInteractive nyilvános API-val szerzünk be azonosítókat é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. For example, 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 csak a személyes Microsoft-fiókok támogatását szeretné korlátozni, cserélje le ezt az értéket consumersa gombra.
  • 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/: .
    • Az országos felhőbeli üzemelő példányok végpontjait a Microsoft Graph dokumentációjában találja.

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 az index.html fájl tartalmát és a Bejelentkezés gombot.

Bejelentkezés tesztelése és kijelentkezés

Az 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 Identitásplatform:

sign-in prompt

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

successful sign-in

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:

profile information from Microsoft Graph

Az alkalmazás működése

Amikor egy felhasználó először választja ki a Bejelentkezés gombot, az acquireTokenInteractive MSAL-csomópont 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-csomópont ezeket a jogkivonatokat is gyorsítótárazza későbbi használatra.

Az azonosító jogkivonat alapvető információkat tartalmaz a felhasználóról, például a megjelenítendő 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.

További 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: