forked from AzureAD/microsoft-authentication-library-for-js
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request AzureAD#2326 from AzureAD/extensions/update-sample
[msal-extensions] Change sample location. Add documentation and diagrams.
- Loading branch information
Showing
11 changed files
with
217 additions
and
98 deletions.
There are no files selected for viewing
Binary file added
BIN
+41.1 KB
extensions/docs/diagrams/png/msal-node-extensions-persistence-cache-plugin.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+91.3 KB
extensions/docs/diagrams/visio/msal-node-extensions-persistence-cache-plugin.vsdx
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
# Microsoft Authentication Extensions for Node | ||
The Microsoft Authentication Extensions for Node offers secure mechanisms for client applications to perform cross-platform token cache serialization and persistence. | ||
|
||
## Overview | ||
MSAL Node requires developers to implement their own logic for persisting the token cache. The MSAL Node extensions aim to provide a robust, secure, and configurable token cache peristence implementation across Windows, Mac, and Linux for public client applications (Desktop clients, CLI applications , etc). It provides mechanisms for encrypting as well as accessing the token cache by multiple processess concurrently. | ||
|
||
Supported platforms are Windows, Mac and Linux: | ||
|
||
- Windows - DPAPI is used for encryption. | ||
- MAC - The MAC KeyChain is used. | ||
- Linux - LibSecret is used for storing to "Secret Service". | ||
|
||
## Code | ||
### Creating the persistence layer | ||
Creating the persistence will differ based on what platform you are targeting. | ||
|
||
#### Windows: | ||
```js | ||
import { FilePersistenceWithDataProtection, DataProtectionScope } from "@azure/msal-node-extensions"; | ||
|
||
const cachePath = "path/to/cache/file.json"; | ||
const dataProtectionScope = DataProtectionScope.CurrentUser; | ||
const optinalEntropy = ""; | ||
const windowsPersistence = FilePersistenceWithDataProtection.create(cachePath, dataProtectionScope, optionalEntropy); | ||
``` | ||
|
||
- cachePath is the path in the file system where the encrypted cache file will be stored. | ||
- dataProtectionScope specifies the scope of the data protection - either the current user or the local machine. You do not need a key to protect or unprotect the data. If you set the scope to CurrentUser, only applications running on your credentials can unprotect the data; however, that means that any application running on your credentials can access the protected data. If you set the scope to LocalMachine, any full-trust application on the computer can unprotect, access, and modify the data. | ||
- optionalEntropy specifies password or other additional entropy used to encrypt the data. | ||
|
||
The FilePersistenceWithDataProtection uses the Win32 CryptProtectData and CryptUnprotectData APIs. For more information on dataProtectionScope, or optionalEntropy, reference the documentation for those APIs. | ||
|
||
#### Mac: | ||
```js | ||
import { KeychainPersistence } from "@azure/msal-node-extensions"; | ||
|
||
const cachePath = "path/to/cache/file.json"; | ||
const serviceName = ""; | ||
const accountName = ""; | ||
const macPersistence = KeychainPersistence.create(cachePath, serviceName, accountName); | ||
``` | ||
|
||
- cachePath is **not** where the cache will be stored. Instead, the extensions update this file with dummy data to update the file's update time, to check if the contents on the keychain should be loaded or not. It is also used as the location for the lock file. | ||
- service name under which the cache is stored the keychain. | ||
- account name under which the cache is stored in the keychain. | ||
|
||
#### Linux: | ||
```js | ||
import { LibSecretPersistence } from "@azure/msal-node-extensions"; | ||
|
||
const cachePath = "path/to/cache/file.json"; | ||
const serviceName = ""; | ||
const accountName = ""; | ||
const linuxPersistence = LibSecretPersistence.create(cachePath, serviceName, accountName); | ||
|
||
``` | ||
|
||
- cachePath is **not** where the cache will be stored. Instead, the extensions update this file with dummy data to update the file's update time, to check if the contents on the secret service (Gnome Keyring for example) should be loaded or not. It is also used as the location for the lock file. | ||
- service name under which the cache is stored the secret service. | ||
- account name under which the cache is stored in the secret service. | ||
|
||
#### All platforms | ||
An unencrypted file persistence, which works across all platforms, is provided for convenience, although not recommended. | ||
|
||
```js | ||
import { FilePersistence } from "@azure/msal-node-extensions"; | ||
|
||
const cachePath = "path/to/cache/file.json"; | ||
const filePersistence = FilePersistence.create(cachePath, serviceName, accountName); | ||
``` | ||
|
||
### Creating the cache plugin | ||
Create the PersistenceCachePlugin, by passing in the persistence object that was created in the previous step. | ||
|
||
```js | ||
import { PersistenceCachePlugin } from "@azure/msal-node-extensions"; | ||
|
||
const persistenceCachePlugin = new PersistenceCachePlugin(windowsPersistence); // or any of the other ones. | ||
``` | ||
|
||
To support concurrent access my multiple processess, the extensions use a file based lock. You can configure the retry number and retry delay for lock acquisition through CrossPlatformLockOptions. | ||
|
||
```js | ||
import { PersistenceCachePlugin } from "@azure/msal-node-extensions"; | ||
|
||
const lockOptions = { | ||
retryNumber: 100, | ||
retryDelay: 50 | ||
} | ||
const persistenceCachePlugin = new PersistenceCachePlugin(windowsPersistence, lockOptions); // or any of the other ones. | ||
``` | ||
|
||
### Setting the PersistenceCachePlugin on the MSAL Node PublicClientApplication configuration | ||
Once you have a PersistenceCachePlugin, that can be set on the MSAL Node PublicClientApplication, by setting it as part of the configuration. | ||
|
||
```js | ||
import { PublicClientApplication } from "@azure/msal-node"; | ||
|
||
const publicClientConfig = { | ||
auth: { | ||
clientId: "", | ||
authority: "", | ||
}, | ||
cache: { | ||
cachePlugin: persistenceCachePlugin; | ||
}, | ||
}; | ||
|
||
const pca = new PublicClientApplication(publicClientConfig); | ||
``` | ||
|
||
Note that MSAL will not read and write to persistence by default. You will have to call PublicClientApplication.tokenCache.readFromPersistence() and PublicClientApplication.tokenCache.writeToPersistence() anytime you trigger and MSAL Node operation that alters the token cache. |
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
/* | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. | ||
*/ | ||
|
||
const express = require("express"); | ||
const msal = require("@azure/msal-node"); | ||
const extensions = require("@azure/msal-node-extensions"); | ||
const process = require("process"); | ||
const path = require("path"); | ||
|
||
const SERVER_PORT = process.env.PORT || 3000; | ||
const cachePath = path.join(__dirname, "./cache.json"); | ||
|
||
createPersistence().then((filePersistence) => { | ||
|
||
const publicClientConfig = { | ||
auth: { | ||
clientId: "99cab759-2aab-420b-91d8-5e3d8d4f063b", | ||
authority: "https://login.microsoftonline.com/90b8faa8-cc95-460e-a618-ee770bee1759", | ||
}, | ||
cache: { | ||
cachePlugin: new extensions.PersistenceCachePlugin(filePersistence) | ||
}, | ||
}; | ||
const pca = new msal.PublicClientApplication(publicClientConfig); | ||
const tokenCache = pca.getTokenCache(); | ||
|
||
// Create Express App and Routes | ||
const app = express(); | ||
|
||
app.get('/', (req, res) => { | ||
const authCodeUrlParameters = { | ||
scopes: ["user.read"], | ||
redirectUri: "http://localhost:3000/redirect", | ||
}; | ||
|
||
// get url to sign user in and consent to scopes needed for application | ||
pca.getAuthCodeUrl(authCodeUrlParameters).then((response) => { | ||
res.redirect(response); | ||
}).catch((error) => console.log(JSON.stringify(error))); | ||
}); | ||
|
||
app.get('/redirect', (req, res) => { | ||
const tokenRequest = { | ||
code: req.query.code, | ||
redirectUri: "http://localhost:3000/redirect", | ||
scopes: ["user.read"], | ||
}; | ||
|
||
pca.acquireTokenByCode(tokenRequest).then((response) => { | ||
console.log("\nResponse: \n", response); | ||
res.sendStatus(200); | ||
if (tokenCache.cacheHasChanged()) { | ||
tokenCache.writeToPersistence(); | ||
} | ||
}).catch((error) => { | ||
console.log(error); | ||
res.status(500).send(error); | ||
}); | ||
}); | ||
|
||
tokenCache.readFromPersistence().then(() => { | ||
app.listen(SERVER_PORT, () => console.log(`Msal Extensions Sample app listening on port ${SERVER_PORT}!`)) | ||
}); | ||
} | ||
); | ||
|
||
/** | ||
* Builds persistence based on operating system. Falls back to storing in plain text. | ||
*/ | ||
async function createPersistence() { | ||
// On Windows, uses a DPAPI encrypted file | ||
if (process.platform === "win32") { | ||
return extensions.FilePersistenceWithDataProtection.create(cachePath, extensions.DataProtectionScope.CurrentUser); | ||
} | ||
|
||
// On Mac, uses keychain. | ||
if (process.platform === "darwin") { | ||
return extensions.KeychainPersistence.create(cachePath, "serviceName", "accountName"); // Replace serviceName and accountName | ||
} | ||
|
||
// On Linux, uses libsecret to store to secret service. Libsecret has to be installed. | ||
if (process.platform === "linux") { | ||
return extensions.LibSecretPersistence.create(cachePath, "serviceName", "accountName"); // Replace serviceName and accountName | ||
} | ||
|
||
throw new Error("Could not create persistence. Platform not supported"); | ||
} |
24 changes: 16 additions & 8 deletions
24
...es/msal-node-extensions/package-lock.json → ...es/msal-node-extensions/package-lock.json
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
File renamed without changes.
This file was deleted.
Oops, something went wrong.