Poznámka
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
V tomto kurzu vytvoříte desktopovou aplikaci Electron, která přihlašuje uživatele a volá Microsoft Graph pomocí autorizačního kódu a PKCE. Desktopová aplikace, kterou sestavíte, používá knihovnu MSAL (Microsoft Authentication Library) pro Node.js.
platí pro: nájemci zaměstnaneckých prostor
externí nájemci (další informace)
V tomto kurzu se naučíte:
- Registrace aplikace na webu Azure Portal
- Vytvořte projekt desktopové aplikace Electron
- Přidání logiky ověřování do aplikace
- Přidání metody pro volání webového rozhraní API
- Přidání podrobností o registraci aplikace
- Otestování aplikace
Požadavky
- Součást pracovního týmu. Můžete použít výchozí adresář nebo nastavit nového tenanta.
- Zaregistrujte novou aplikaci v Centru pro správu Microsoft Entra, která je nakonfigurovaná jenom pro účty v tomto organizačním adresáři. Další podrobnosti najdete v tématu Registrace aplikace . Na stránce Přehled aplikace si poznamenejte následující hodnoty pro pozdější použití:
- ID aplikace (klienta)
- ID adresáře (klienta)
- Přidejte následující URI přesměrování pomocí konfigurace platformy Mobilní a desktopové aplikace. Další podrobnosti najdete v tématu Jak přidat URI pro přesměrování ve vaší aplikaci.
-
Přesměrovací URI:
http://localhost
-
Přesměrovací URI:
- Node.js
- Elektron
- Visual Studio Code nebo jiný editor kódu
Vytvoření projektu
Poznámka:
Elektronový vzorek poskytnutý v tomto kurzu je speciálně navržený tak, aby fungoval s MSAL-node. MsAL-browser není podporován v aplikacích Electron. Ujistěte se, že jste dokončili následující kroky pro správné nastavení projektu.
Vytvořte složku pro hostování aplikace, například ElectronDesktopApp.
Nejprve přejděte do adresáře projektu v terminálu a spusťte následující
npm
příkazy: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
Pak vytvořte složku s názvem Aplikace. V této složce vytvořte soubor s názvem index.html , který bude sloužit jako uživatelské rozhraní. Přidejte do ní následující kód:
<!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>
Dále vytvořte soubor s názvem main.js a přidejte následující kód:
/* * 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); });
Ve výše uvedeném fragmentu kódu inicializujeme objekt hlavního okna Elektron a vytvoříme některé obslužné rutiny událostí pro interakce s oknem Elektron. Importujeme také parametry konfigurace, vytvoříme instanci třídy authProvider pro zpracování přihlášení, odhlášení a získání tokenu a zavoláme rozhraní Microsoft Graph API.
Ve stejné složce (App) vytvořte další soubor s názvem renderer.js a přidejte následující kód:
// 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); }
Metody rendereru jsou vystaveny skriptem, který se nachází v souboru preload.js, aby vykreslovací modul získal přístup k Node API
způsobem, který je zabezpečený a řízený.
Pak vytvořte nový soubor preload.js a přidejte následující kód:
// 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)); }, });
Tento skript předběžného načtení zpřístupňuje rozhraní API rendereru, které poskytuje procesům rendereru řízený přístup k některým Node APIs
použitím kanálů IPC nakonfigurovaných pro komunikaci mezi hlavními a vykreslovacími procesy.
Nakonec vytvořte soubor s názvem constants.js, který uloží konstanty řetězců pro popis událostí aplikace:
/* * 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, }
Teď máte jednoduché grafické uživatelské rozhraní a interakce pro aplikaci Elektron. Po dokončení zbývající části kurzu by struktura souborů a složek projektu měla vypadat nějak takto:
ElectronDesktopApp/
├── App
│ ├── AuthProvider.js
│ ├── constants.js
│ ├── graph.js
│ ├── index.html
| ├── main.js
| ├── preload.js
| ├── renderer.js
│ ├── authConfig.js
├── package.json
Přidání logiky ověřování do aplikace
Ve složce Aplikace vytvořte soubor s názvem AuthProvider.js. Soubor AuthProvider.js bude obsahovat třídu zprostředkovatele ověřování, která bude zpracovávat přihlašovací údaje, odhlášení, získání tokenu, výběr účtu a související úlohy ověřování pomocí uzlu MSAL. Přidejte do ní následující kód:
/*
* 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;
Ve výše uvedeném fragmentu kódu jsme nejprve inicializovali uzel PublicClientApplication
MSAL předáním objektu konfigurace (msalConfig
). Pak jsme zpřístupnili metody login
, logout
a getToken
, které má volat hlavní modul (main.js). V login
a getToken
, získáme ID a přístupové tokeny pomocí veřejného rozhraní API MSAL Node acquireTokenInteractive
.
Přidání sady Microsoft Graph SDK
Vytvořte soubor s názvem graph.js. Soubor graph.js bude obsahovat instanci klienta sady Microsoft Graph SDK pro usnadnění přístupu k datům v rozhraní Microsoft Graph API pomocí přístupového tokenu získaného uzlem MSAL:
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;
Přidání podrobností o registraci aplikace
Vytvořte soubor prostředí pro uložení podrobností o registraci aplikace, které se použijí při získávání tokenů. Uděláte to tak, že vytvoříte soubor s názvem authConfig.js uvnitř kořenové složky ukázky (ElectronDesktopApp) a přidáte následující kód:
/*
* 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,
};
Vyplňte tyto podrobnosti hodnotami, které získáte z portálu pro registraci aplikací Azure:
-
Enter_the_Tenant_Id_here
by měla být jedna z následujících možností:- Pokud vaše aplikace podporuje účty v tomto organizačním adresáři, nahraďte tuto hodnotu ID tenanta nebo názvem tenanta. Například
contoso.microsoft.com
. - Pokud vaše aplikace podporuje účty v libovolném organizačním adresáři, nahraďte tuto hodnotu
organizations
hodnotou . - Pokud vaše aplikace podporuje účty v libovolném organizačním adresáři a osobních účtech Microsoft, nahraďte tuto hodnotu
common
hodnotou . - Chcete-li omezit podporu pouze na osobní účty Microsoft, nahraďte tuto hodnotu
consumers
hodnotou .
- Pokud vaše aplikace podporuje účty v tomto organizačním adresáři, nahraďte tuto hodnotu ID tenanta nebo názvem tenanta. Například
-
Enter_the_Application_Id_Here
: ID aplikace (klienta) aplikace, kterou jste zaregistrovali. -
Enter_the_Cloud_Instance_Id_Here
: Cloudová instance Azure, ve které je vaše aplikace zaregistrovaná.- V případě hlavního (nebo globálního) cloudu Azure zadejte
https://login.microsoftonline.com/
. - Pro národní cloudy (například Čína) najdete odpovídající hodnoty v národních cloudech.
- V případě hlavního (nebo globálního) cloudu Azure zadejte
-
Enter_the_Graph_Endpoint_Here
je instance rozhraní Microsoft Graph API, se kterým by aplikace měla komunikovat.- V případě globálního koncového bodu rozhraní Microsoft Graph API nahraďte obě instance tohoto řetězce řetězcem
https://graph.microsoft.com/
. - Koncové body v národních cloudových nasazeních naleznete v dokumentaci k nasazením národních cloudů Microsoft Graph.
- V případě globálního koncového bodu rozhraní Microsoft Graph API nahraďte obě instance tohoto řetězce řetězcem
Otestování aplikace
Dokončili jste vytváření aplikace a jste připraveni spustit desktopovou aplikaci Electron a otestovat její funkčnost.
- Spusťte aplikaci spuštěním následujícího příkazu v kořenové složce projektu:
electron App/main.js
- V hlavním okně aplikace byste měli vidět obsah souboru index.html a tlačítko Přihlásit se.
Otestování přihlášení a odhlášení
Po načtení souboru index.html vyberte Přihlásit se. Zobrazí se výzva k přihlášení pomocí platformy Microsoft Identity Platform:
Pokud souhlasíte s požadovanými oprávněními, webové aplikace zobrazí vaše uživatelské jméno a podepisují úspěšné přihlášení:
Testování volání webového rozhraní API
Po přihlášení vyberte Zobrazit profil a zobrazte informace o profilu uživatele vrácené v odpovědi z volání rozhraní Microsoft Graph API. Po vyjádření souhlasu zobrazíte informace o profilu vrácené v odpovědi:
Jak aplikace funguje
Když uživatel poprvé vybere tlačítko Přihlásit se, acquireTokenInteractive
metoda MSAL Node. Tato metoda přesměruje uživatele na přihlášení pomocí koncového bodu Microsoft Identity Platform a ověří přihlašovací údaje uživatele, získá autorizační kód a pak vymění tento kód za token ID, přístupový token a obnovovací token. Uzel MSAL tyto tokeny také ukládá do mezipaměti pro budoucí použití.
Token ID obsahuje základní informace o uživateli, například zobrazované jméno. Přístupový token má omezenou životnost a vyprší po 24 hodinách. Pokud plánujete používat tyto tokeny pro přístup k chráněnému prostředku, back-endový server ho musí ověřit, aby se zajistilo, že token byl vydán platnému uživateli pro vaši aplikaci.
Desktopová aplikace, kterou jste vytvořili v tomto kurzu, provádí REST volání na Microsoft Graph API s použitím přístupového tokenu jako bearer tokenu v hlavičce požadavku (RFC 6750).
Rozhraní Microsoft Graph API vyžaduje obor user.read ke čtení profilu uživatele. Ve výchozím nastavení se tento obor automaticky přidá do každé aplikace zaregistrované na webu Azure Portal. Další rozhraní API pro Microsoft Graph a vlastní rozhraní API pro back-endový server mohou vyžadovat další oprávnění. Rozhraní Microsoft Graph API například vyžaduje obor Mail.Read , aby bylo možné zobrazit seznam e-mailů uživatele.
Při přidávání oborů se uživatelům může zobrazit výzva k poskytnutí dalšího souhlasu s přidanými obory.
Nápověda a podpora
Pokud potřebujete pomoc, chcete nahlásit problém nebo se chcete dozvědět o možnostech podpory, přečtěte si nápovědu a podporu pro vývojáře.
Další kroky
Pokud se chcete podrobněji podívat na vývoj desktopových aplikací Node.js a Elektron na platformě Microsoft Identity Platform, projděte si naši sérii scénářů s více částmi: