Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
I den här självstudien skapar du ett Electron-skrivbordsprogram som loggar in användare och anropar Microsoft Graph med hjälp av auktoriseringskodflödet med PKCE. Den skrivbordsapplikation du bygger använder Microsoft Authentication Library (MSAL) för Node.js.
              Gäller för:  Personalklienter (läs mer)
 Personalklienter (läs mer)
I den här handledningen kommer du att:
- Registrera applikationen i Azure-portalen
- Skapa ett Electron-projekt för skrivbordsappar
- Lägg till autentiseringslogik i din app
- Lägg till en metod för att anropa ett webb-API
- Lägg till appregistreringsdetaljer
- Testa appen
Förutsättningar
- En hyresgäst för arbetskraft. Du kan använda din standardkatalog eller konfigurera en ny klient.
- Registrera en ny app i administrationscentret för Microsoft Entra, som endast konfigurerats för konton i den här organisationskatalogen. Mer information finns i Registrera ett program . Registrera följande värden från programöversiktssidan för senare användning: - App-ID (klient-ID)
- Katalog-ID (hyresgäst)
 
- Lägg till följande omdirigerings-URI:er med hjälp av plattformskonfigurationen för mobil- och skrivbordsprogram . Mer information finns i Så här lägger du till en omdirigerings-URI i ditt program .
- 
              omdirigerings-URI: http://localhost
 
- 
              omdirigerings-URI: 
- Node.js
- Elektron
- Visual Studio Code eller en annan kodredigerare
Skapa projektet
Anmärkning
Elektronexemplet i den här självstudien är särskilt utformat för att fungera med MSAL-noden. MSAL-browser stöds inte i Electron-program. Se till att du slutför följande steg för att konfigurera projektet korrekt.
Skapa en mapp för att hysa din applikation, till exempel ElectronDesktopApp.
- Först, byt till din projektkatalog i din terminal och kör sedan följande - npmkommandon:- 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
- Skapa sedan en mapp som heter App. Inuti den här mappen, skapa en fil med namnet index.html som kommer att fungera som UI. Lägg till följande kod där: - <!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>
- Nästa steg, skapa en fil med namnet main.js och lägg till följande kod: - /* * 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); });
I kodexemplet ovan initierar vi ett Electron-huvudfönsterobjekt och skapar några händelsehanterare för interaktioner med Electron-fönstret. Vi importerar också konfigurationsparametrar, instansierar authProvider-klassen för att hantera inloggning, utloggning och tokenförvärv, och anropar Microsoft Graph API.
- I samma mapp (App), skapa en annan fil med namnet renderer.js och lägg till följande kod: - // 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); }
Renderar-metoderna exponeras av förladdningsskriptet som finns i preload.js filen för att ge renderaren tillgång till Node API på ett säkert och kontrollerat sätt.
- Skapa därefter en ny fil preload.js och lägg till följande kod: - // 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)); }, });
Det här förladdningsskriptet exponerar ett renderar-API för att ge renderarprocessen kontrollerad tillgång till vissa Node APIs genom att tillämpa IPC-kanaler som har konfigurerats för kommunikation mellan huvudprocessen och renderarprocessen.
- Slutligen, skapa en fil som heter constants.js som kommer att lagra strängkonstanter för att beskriva applikationens händelser. - /* * 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, }
Du har nu en enkel GUI och interaktioner för din Electron-app. Efter att ha slutfört resten av handledningen bör fil- och mappstrukturen i ditt projekt se ut ungefär som följande:
ElectronDesktopApp/
├── App
│   ├── AuthProvider.js
│   ├── constants.js
│   ├── graph.js
│   ├── index.html
|   ├── main.js
|   ├── preload.js
|   ├── renderer.js
│   ├── authConfig.js
├── package.json
Lägg till autentiseringslogik i din app
I App-mappen, skapa en fil som heter AuthProvider.js. Filen AuthProvider.js kommer att innehålla en autentiseringsleverantörsklass som hanterar inloggning, utloggning, tokenanskaffning, kontoval och relaterade autentiseringsuppgifter med MSAL Node. Lägg till följande kod där:
/*
 * 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://free.blessedness.top/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;
I kodfragmentet ovan initierade vi först MSAL Node PublicClientApplication genom att skicka ett konfigurationsobjekt (msalConfig). Vi avslöjade loginsedan , logout och getToken metoder som ska anropas av huvudmodulen (main.js). I login och getToken erhåller vi ID- och behörighetstokens med hjälp av MSAL Nodes acquireTokenInteractive offentliga API.
Lägga till Microsoft Graph SDK
Skapa en fil med namnet graph.js. Filen graph.js kommer att innehålla en instans av Microsoft Graph SDK-klienten för att underlätta åtkomsten till data på Microsoft Graph API, med hjälp av den åtkomsttoken som erhålls av MSAL Node.
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;
Lägg till appregistreringsdetaljer
Skapa en miljöfil för att lagra den appregistreringsinformation som ska användas vid anskaffning av token. För att göra detta, skapa en fil som heter authConfig.js i rotmappen av exemplet (ElectronDesktopApp), och lägg till följande kod:
/*
 * 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,
};
Fyll i dessa detaljer med de värden du får från Azure-appregistreringsportalen.
- 
              Enter_the_Tenant_Id_hereborde vara ett av följande:- Om din applikation stöder konton i denna organisationskatalog, ersätt detta värde med hyresgästens ID eller hyresgästnamn. Till exempel, contoso.microsoft.com.
- Om ditt program stöder konton i en organisationskatalog ersätter du det här värdet med organizations.
- Om din applikation stöder konton i vilken organisationskatalog som helst och personliga Microsoft-konton, ersätt det här värdet med common.
- För att begränsa supporten till endast personliga Microsoft-konton, ersätt detta värde med consumers.
 
- Om din applikation stöder konton i denna organisationskatalog, ersätt detta värde med hyresgästens ID eller hyresgästnamn. Till exempel, 
- 
              Enter_the_Application_Id_Here: Program ID (klient-ID) för den applikation du registrerade.
- 
              Enter_the_Cloud_Instance_Id_Here: Den Azure-molninstans där din applikation är registrerad.- För huvud (eller global) Azure-molnet, ange https://login.microsoftonline.com/.
- För nationella moln (till exempel, Kina), kan du hitta lämpliga värden i Nationella moln.
 
- För huvud (eller global) Azure-molnet, ange 
- 
              Enter_the_Graph_Endpoint_Hereär den instans av Microsoft Graph API som applikationen ska kommunicera med.- För den globala Microsoft Graph API-slutpunkten, ersätt båda förekomsterna av denna sträng med https://graph.microsoft.com/.
- För slutpunkter i nationella molninstallationer, se Nationella molninstallationer i Microsoft Graph-dokumentationen.
 
- För den globala Microsoft Graph API-slutpunkten, ersätt båda förekomsterna av denna sträng med 
Testa appen
Du har slutfört skapandet av applikationen och är nu redo att starta Electron-skrivbordsappen och testa appens funktionalitet.
- Starta appen genom att köra följande kommando från roten av din projektmapp:
electron App/main.js
- I programmets huvudfönster bör du se innehållet i din index.html-fil och knappen Logga in.
Testa att logga in och logga ut
Efter att filen index.html har laddats, välj Logga in. Du uppmanas att logga in med Microsoft Identity Platform:
              
               
              
              
            
Om du samtycker till de begärda behörigheterna, visar webbapplikationen ditt användarnamn, vilket betyder att du har loggat in framgångsrikt.
              
               
              
              
            
Testa web API-anrop
När du har loggat in väljer du Se profil för att visa användarprofilinformationen som returneras i svaret från anropet till Microsoft Graph API. Efter samtycke kommer du att se profilinformationen som returneras i svaret.
              
               
              
              
            
Hur applikationen fungerar
När en användare väljer Sign In-knappen för första gången, anropas acquireTokenInteractive-metoden i MSAL Node. Denna metod omdirigerar användaren till att logga in med Microsofts identitetsplattformsslutpunkt och validerar användarens referenser, erhåller en auktoriseringskod och byter sedan ut den koden mot en ID-token, åtkomsttoken och förnyelsetoken. MSAL Node cachar också dessa token för framtida användning.
ID-tokenet innehåller grundläggande information om användaren, såsom deras visningsnamn. Åtkomsttokenet har en begränsad livslängd och upphör att gälla efter 24 timmar. Om du planerar att använda dessa token för att få åtkomst till skyddade resurser måste din back-end server validera den för att garantera att token utfärdades till en giltig användare för din applikation.
Den skrivbordsapp som du har skapat i denna handledning gör ett REST-anrop till Microsoft Graph API genom att använda en åtkomsttoken som bärartoken i begäranshuvudet (RFC 6750).
Microsoft Graph API kräver user.read-åtkomst för att läsa en användares profil. Som standard läggs det här omfånget automatiskt till i varje program som är registrerat i Azure Portal. Andra API:er för Microsoft Graph, och anpassade API:er för din backendserver, kan kräva extra behörigheter. Till exempel kräver Microsoft Graph API omfånget Mail.Read för att kunna visa en lista över användarens e-post.
När du lägger till åtkomstområden kan dina användare uppmanas att ge ett nytt samtycke för de tillagda åtkomstområdena.
Hjälp och support
Om du behöver hjälp, vill rapportera ett problem, eller vill lära dig mer om dina supportalternativ, se Hjälp och support för utvecklare.
Nästa steg
Om du vill fördjupa dig i utveckling av Node.js- och Electron-skrivbordsapplikationer på Microsofts identitetsplattform, se vår serie om flerdelade scenarier.