diff --git a/packages/providers/mgt-electron-provider/README.md b/packages/providers/mgt-electron-provider/README.md index ea00b44327..f0079ceca2 100644 --- a/packages/providers/mgt-electron-provider/README.md +++ b/packages/providers/mgt-electron-provider/README.md @@ -4,7 +4,7 @@ The [Microsoft Graph Toolkit (mgt)](https://aka.ms/mgt) library is a collection The `@microsoft/mgt-electron-provider` package exposes the `ElectronAuthenticator` and `ElectronProvider` classes which use [MSAL node](https://www.npmjs.com/package/@azure/msal-node) to sign in users and acquire tokens to use with Microsoft Graph. -## Usage +## Usage without Context Bridge 1. Install the packages @@ -48,6 +48,87 @@ Note : Make sure `nodeIntegration` is set to `true` under `webPreferences` while See [provider usage documentation](https://learn.microsoft.com/graph/toolkit/providers) to learn about how to use the providers with the mgt components, to sign in/sign out, get access tokens, call Microsoft Graph, and more. See [Electron provider documentation](https://learn.microsoft.com/graph/toolkit/providers/electron). +## Usage with Context Bridge + +1. Install the packages + + ```bash + npm install @microsoft/mgt-element @microsoft/mgt-electron-provider + ``` + +2. Setup the context bridge in your preload script (eg. preload.ts) + + ```ts + import { contextBridge } from 'electron'; + import { ElectronContextBridgeProvider } from '@microsoft/mgt-electron-provider/dist/Provider'; + + // can be named anything, like "electronApi" + contextBridge.exposeInMainWorld("main", { + electronProvider: { + mgtAuthState: (callback: (event: IpcRendererEvent, authState: string) => void) => ipcRenderer.on('mgtAuthState', callback), + token: (options?: AuthenticationProviderOptions) => ipcRenderer.invoke('token', options), + login: () => ipcRenderer.invoke('login'), + logout: () => ipcRenderer.invoke('logout'), + }, + }); + ``` + + Expose the ElectronProvider methods through the context bridge. Here, we've named the api "main" for the main window, but it can be named anything. We've also made sure to put them under `electronProvider` to separate them from other methods you may add. These methods must match the `IContextBridgeImpl` interface in the `@microsoft/mgt-electron-provider` package. + +3. Globally augment the `Window` interface in a declaration file (eg. preload.d.ts) + + ```ts + import { IContextBridgeImpl } from '@microsoft/mgt-electron-provider/dist/Provider'; + + export declare global { + interface Window { + // can be named anything, like "electronApi" + main: { + electronProvider: IContextBridgeImpl; + } + } + } + ``` + +3. Initialize the provider in your renderer process (Front end, eg. renderer.ts) + + ```ts + import {Providers} from '@microsoft/mgt-element'; + import {ElectronContextBridgeProvider} from '@microsoft/mgt-electron-provider/dist/Provider'; + + // initialize the auth provider globally + Providers.globalProvider = new ElectronContextBridgeProvider(window.main.electronProvider) + ``` + +4. Initialize ElectronAuthenticator in Main.ts (Back end) + + ```ts + import { ElectronAuthenticator, MsalElectronConfig } from '@microsoft/mgt-electron-provider/dist/Authenticator'; + ... + let mainWindow = new BrowserWindow({ + width: 800, + height: 800, + webPreferences: { + nodeIntegration: false // make sure this is false, we're using context bridge + } + }); + + let config: MsalElectronConfig = { + clientId: '', + authority: '', // optional, uses common authority by default + mainWindow: mainWindow, // this is the BrowserWindow instance that requires authentication + scopes: [ + 'user.read', + ], + }; + + ElectronAuthenticator.initialize(config); + ``` + +Note : Make sure `nodeIntegration` is set to `false` under `webPreferences` while creating a new BrowserWindow instance. This is because we're using context bridge to communicate between the main and renderer processes. + +See [provider usage documentation](https://learn.microsoft.com/graph/toolkit/providers) to learn about how to use the providers with the mgt components, to sign in/sign out, get access tokens, call Microsoft Graph, and more. See [Electron provider documentation](https://learn.microsoft.com/graph/toolkit/providers/electron). + ### Cache Plugin [MSAL Node](https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-node) supports an in-memory cache by default and provides the ICachePlugin interface to perform cache serialization, but does not provide a default way of storing the token cache to disk. If you need persistent cache storage to enable silent log-ins or cross-platform caching, we recommend using the default implementation provided by MSAL Node [here](https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/extensions/msal-node-extensions). You can import this plugin, and pass the instance of the cache plugin while initializing ElectronAuthenticator. diff --git a/packages/providers/mgt-electron-provider/src/Provider/ElectronContextBridgeProvider.ts b/packages/providers/mgt-electron-provider/src/Provider/ElectronContextBridgeProvider.ts new file mode 100644 index 0000000000..5ea40de6c8 --- /dev/null +++ b/packages/providers/mgt-electron-provider/src/Provider/ElectronContextBridgeProvider.ts @@ -0,0 +1,109 @@ +/** + * ------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. + * See License in the project root for license information. + * ------------------------------------------------------------------------------------------- + */ + +import { + GraphEndpoint, + IProvider, + MICROSOFT_GRAPH_DEFAULT_ENDPOINT, + ProviderState, + Providers, + createFromProvider +} from '@microsoft/mgt-element'; +import { AuthenticationProviderOptions } from '@microsoft/microsoft-graph-client'; +import type { IpcRendererEvent } from 'electron'; + +/** + * The interface that describes the shape of the context bridge + * that is used to communicate between the main and renderer processes. + */ +export interface IContextBridgeImpl { + mgtAuthState: (callback: (event: IpcRendererEvent, authState: string) => void) => void; + token: (options?: AuthenticationProviderOptions) => Promise; + login: () => Promise; + logout: () => Promise; +} + +/** + * ElectronProvider class to be instantiated in the a preload script. + * Responsible for communicating with ElectronAuthenticator in the main process to acquire tokens. + * + * This class uses the `contextBridge` to communicate with the main process, + * which must be passed in as a constructor parameter. + * + * @export + * @class ElectronProvider + * @extends {IProvider} + */ +export class ElectronContextBridgeProvider extends IProvider { + /** + * Name used for analytics + * + * @readonly + * @memberof IProvider + */ + public get name() { + return 'MgtElectronContextBridgeProvider'; + } + + private readonly contextBridge: IContextBridgeImpl; + + constructor(contextBridge: IContextBridgeImpl, baseUrl: GraphEndpoint = MICROSOFT_GRAPH_DEFAULT_ENDPOINT) { + super(); + this.baseURL = baseUrl; + this.contextBridge = contextBridge; + this.graph = createFromProvider(this); + this.setupProvider(); + } + + /** + * Sets up messaging between main and renderer to receive SignedIn/SignedOut state information + * + * @memberof ElectronProvider + */ + setupProvider() { + this.contextBridge.mgtAuthState((event, authState) => { + if (authState === 'logged_in') { + Providers.globalProvider.setState(ProviderState.SignedIn); + } else if (authState === 'logged_out') { + Providers.globalProvider.setState(ProviderState.SignedOut); + } + }); + } + + /** + * Gets access token (called by MGT components) + * + * @param {AuthenticationProviderOptions} [options] + * @return {*} {Promise} + * @memberof ElectronProvider + */ + async getAccessToken(options?: AuthenticationProviderOptions): Promise { + const token = await this.contextBridge.token(options); + return token; + } + + /** + * Log in to set account information (called by mgt-login) + * + * @return {*} {Promise} + * @memberof ElectronProvider + */ + async login(): Promise { + Providers.globalProvider.setState(ProviderState.Loading); + await this.contextBridge.login(); + } + + /** + * Log out (called by mgt-login) + * + * @return {*} {Promise} + * @memberof ElectronProvider + */ + async logout(): Promise { + await this.contextBridge.logout(); + } +} diff --git a/packages/providers/mgt-electron-provider/src/Provider/index.ts b/packages/providers/mgt-electron-provider/src/Provider/index.ts index 85bcd82afc..7601f9c547 100644 --- a/packages/providers/mgt-electron-provider/src/Provider/index.ts +++ b/packages/providers/mgt-electron-provider/src/Provider/index.ts @@ -5,4 +5,5 @@ * ------------------------------------------------------------------------------------------- */ +export * from './ElectronContextBridgeProvider'; export * from './ElectronProvider';