Skip to content

Commit

Permalink
Merge pull request AzureAD#2326 from AzureAD/extensions/update-sample
Browse files Browse the repository at this point in the history
[msal-extensions] Change sample location. Add documentation and diagrams.
  • Loading branch information
sangonzal authored Sep 25, 2020
2 parents 8dcb135 + 8961ed3 commit da2fda9
Show file tree
Hide file tree
Showing 11 changed files with 217 additions and 98 deletions.
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 not shown.
Binary file not shown.
112 changes: 112 additions & 0 deletions extensions/docs/msal-node-extensions.md
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.
89 changes: 89 additions & 0 deletions extensions/samples/msal-node-extensions/index.js
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");
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

File renamed without changes.
90 changes: 0 additions & 90 deletions samples/msal-node-extensions/index.js

This file was deleted.

0 comments on commit da2fda9

Please sign in to comment.