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 Microsoft Authentication Library (MSAL) használatával Node.js.
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
- Node.js
- Elektron
- Visual Studio Code vagy más kódszerkesztő
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.
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
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>
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.
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 a preload.js fájlban található előretöltési szkript teszi elérhetővé, hogy a renderelő biztonságos és szabályozott módon férhessen Node API
hozzá
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.
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 login
a logout
getToken
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. 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őre
organizations
: . - 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
consumers
a gombra.
- 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:
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.
- A fő (vagy globális) Azure-felhőbe írja be a következőt
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őre
https://graph.microsoft.com/
: . - Az országos felhőbeli üzemelő példányok végpontjait a Microsoft Graph dokumentációjában találja.
- A globális Microsoft Graph API-végpont esetében cserélje le a sztring mindkét példányát a következőre
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.
- Indítsa el az alkalmazást a következő parancs futtatásával a projektmappában:
electron App/main.js
- 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 Identitásplatform:
Ha ön hozzájárul a kért engedélyekhez, a webalkalmazások megjelenítik a felhasználónevet, és sikeres bejelentkezést jeleznek:
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:
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.
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: