Skip to content

Commit c1ff9e1

Browse files
authored
Merge pull request #1809 from AzureAD/msal-node-silent-flow-client
[msal-node] SilentFlow node interface
2 parents 8ec2586 + 98ce968 commit c1ff9e1

File tree

9 files changed

+596
-38
lines changed

9 files changed

+596
-38
lines changed

lib/msal-common/src/cache/utils/CacheTypes.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ import { AccessTokenEntity } from "../entities/AccessTokenEntity";
99
import { RefreshTokenEntity } from "../entities/RefreshTokenEntity";
1010
import { AppMetadataEntity } from "../entities/AppMetadataEntity";
1111

12-
export type AccountCache = { [key: string]: AccountEntity };
13-
export type IdTokenCache = { [key: string]: IdTokenEntity };
14-
export type AccessTokenCache = { [key: string]: AccessTokenEntity };
15-
export type RefreshTokenCache = { [key: string]: RefreshTokenEntity };
16-
export type AppMetadataCache = { [key: string]: AppMetadataEntity };
12+
export type AccountCache = Record<string, AccountEntity>;
13+
export type IdTokenCache = Record<string, IdTokenEntity>;
14+
export type AccessTokenCache = Record<string, AccessTokenEntity>;
15+
export type RefreshTokenCache = Record<string, RefreshTokenEntity>;
16+
export type AppMetadataCache = Record<string, AppMetadataEntity>;
1717
export type CredentialCache = {
1818
idTokens: IdTokenCache;
1919
accessTokens: AccessTokenCache;

lib/msal-node/src/cache/Storage.ts

+12-17
Original file line numberDiff line numberDiff line change
@@ -240,31 +240,26 @@ export class Storage extends CacheManager {
240240
*/
241241
getKeys(): string[] {
242242
// read inMemoryCache
243-
const cache = this.getCache();
244-
let cacheKeys: string[] = [];
245-
246-
// read all keys
247-
Object.keys(cache).forEach(key => {
248-
Object.keys(key).forEach(internalKey => {
249-
cacheKeys.push(internalKey);
250-
});
251-
});
252-
253-
return cacheKeys;
243+
const cache: InMemoryCache= this.getCache() as InMemoryCache;
244+
return [
245+
...Object.keys(cache.accounts),
246+
...Object.keys(cache.idTokens),
247+
...Object.keys(cache.accessTokens),
248+
...Object.keys(cache.refreshTokens),
249+
...Object.keys(cache.appMetadata),
250+
];
254251
}
255252

256253
/**
257254
* Clears all cache entries created by MSAL (except tokens).
258255
*/
259256
clear(): void {
260257
// read inMemoryCache
261-
const cache = this.getCache();
258+
const cacheKeys = this.getKeys();
262259

263-
// read all keys
264-
Object.keys(cache).forEach(key => {
265-
Object.keys(key).forEach(internalKey => {
266-
this.removeItem(internalKey);
267-
});
260+
// delete each element
261+
cacheKeys.forEach(key => {
262+
this.removeItem(key);
268263
});
269264
this.emitChange();
270265
}

lib/msal-node/src/cache/TokenCache.ts

+20-10
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,8 @@
44
*/
55

66
import { Storage } from './Storage';
7-
import { ClientAuthError, StringUtils } from '@azure/msal-common';
8-
import {
9-
InMemoryCache,
10-
JsonCache,
11-
SerializedAccountEntity,
12-
SerializedAccessTokenEntity,
13-
SerializedRefreshTokenEntity,
14-
SerializedIdTokenEntity,
15-
SerializedAppMetadataEntity
16-
} from './serializer/SerializerTypes';
7+
import { ClientAuthError, StringUtils, AccountEntity, AccountInfo} from '@azure/msal-common';
8+
import { InMemoryCache, JsonCache, SerializedAccountEntity, SerializedAccessTokenEntity, SerializedRefreshTokenEntity, SerializedIdTokenEntity, SerializedAppMetadataEntity } from './serializer/SerializerTypes';
179
import { ICachePlugin } from './ICachePlugin';
1810
import { Deserializer } from './serializer/Deserializer';
1911
import { Serializer } from './serializer/Serializer';
@@ -131,6 +123,24 @@ export class TokenCache {
131123
}
132124
}
133125

126+
127+
/**
128+
* API that retrieves all accounts currently in cache to the user
129+
*/
130+
getAllAccounts(): AccountInfo[] {
131+
return this.storage.getAllAccounts();
132+
}
133+
134+
/**
135+
* API to remove a specific account and the relevant data from cache
136+
* @param account
137+
*/
138+
removeAccount(account: AccountInfo) {
139+
this.storage.removeAccount(
140+
AccountEntity.generateAccountCacheKey(account)
141+
);
142+
}
143+
134144
/**
135145
* Called when the cache has changed state.
136146
*/

lib/msal-node/src/client/ClientApplication.ts

+22-6
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ import {
1616
ClientAuthError,
1717
Constants,
1818
TrustedAuthority,
19-
AccountInfo,
20-
BaseAuthRequest
19+
BaseAuthRequest,
20+
SilentFlowRequest,
21+
SilentFlowClient,
2122
} from '@azure/msal-common';
2223
import { Configuration, buildAppConfiguration } from '../config/Configuration';
2324
import { CryptoProvider } from '../crypto/CryptoProvider';
@@ -106,6 +107,25 @@ export abstract class ClientApplication {
106107
return refreshTokenClient.acquireToken(this.initializeRequestScopes(request) as RefreshTokenRequest);
107108
}
108109

110+
/**
111+
* Acquires a token silently when a user specifies the account the token is requested for.
112+
*
113+
* This API expects the user to provide an account object and looks into the cache to retrieve the token if present.
114+
* There is also an optional "forceRefresh" boolean the user can send, to bypass the cache for access_token and id_token
115+
* In case the refresh_token is expired or not found, an error is thrown
116+
* and the guidance is for the user to call any interactive token acquisition API (eg: acquireTokenByCode())
117+
* @param request
118+
*/
119+
async acquireTokenSilent(request: SilentFlowRequest): Promise<AuthenticationResult> {
120+
const silentFlowClientConfig = await this.buildOauthClientConfiguration(
121+
request.authority
122+
);
123+
const silentFlowClient = new SilentFlowClient(
124+
silentFlowClientConfig
125+
);
126+
return silentFlowClient.acquireToken(this.initializeRequestScopes(request) as SilentFlowRequest);
127+
}
128+
109129
getCacheManager(): TokenCache {
110130
return this.tokenCache;
111131
}
@@ -180,8 +200,4 @@ export abstract class ClientApplication {
180200

181201
return this._authority;
182202
}
183-
184-
getAllAccounts(): AccountInfo[] {
185-
return this.storage.getAllAccounts();
186-
}
187203
}

samples/msal-node-samples/msal-node-auth-code/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -72,5 +72,6 @@ app.get('/redirect', (req, res) => {
7272

7373
msalCacheManager.readFromPersistence().then(() => {
7474
app.listen(SERVER_PORT, () => console.log(`Msal Node Auth Code Sample app listening on port ${SERVER_PORT}!`))
75+
console.log("Accounts: \n", msalCacheManager.getAllAccounts());
7576
});
7677

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
{
2+
"Account": {
3+
"uid.utid-login.microsoftonline.com-microsoft": {
4+
"username": "John Doe",
5+
"local_account_id": "object1234",
6+
"realm": "microsoft",
7+
"environment": "login.microsoftonline.com",
8+
"home_account_id": "uid.utid",
9+
"authority_type": "MSSTS",
10+
"client_info": "base64encodedjson"
11+
}
12+
},
13+
"AccessToken": {
14+
"uid.utid-login.microsoftonline.com-accesstoken-mock_client_id-microsoft-scope1 scope2 scope3": {
15+
"environment": "login.microsoftonline.com",
16+
"credential_type": "AccessToken",
17+
"secret": "an access token",
18+
"realm": "microsoft",
19+
"target": "scope1 scope2 scope3",
20+
"client_id": "mock_client_id",
21+
"cached_at": "1000",
22+
"home_account_id": "uid.utid",
23+
"extended_expires_on": "4600",
24+
"expires_on": "4600"
25+
},
26+
"uid.utid-login.microsoftonline.com-accesstoken-mock_client_id-microsoft-scope4 scope5": {
27+
"environment": "login.microsoftonline.com",
28+
"credential_type": "AccessToken",
29+
"secret": "an access token",
30+
"realm": "microsoft",
31+
"target": "scope4 scope5",
32+
"client_id": "mock_client_id",
33+
"cached_at": "1000",
34+
"home_account_id": "uid.utid",
35+
"extended_expires_on": "4600",
36+
"expires_on": "4600"
37+
}
38+
},
39+
"RefreshToken": {
40+
"uid.utid-login.microsoftonline.com-refreshtoken-mock_client_id--": {
41+
"environment": "login.microsoftonline.com",
42+
"credential_type": "RefreshToken",
43+
"secret": "a refresh token",
44+
"client_id": "mock_client_id",
45+
"home_account_id": "uid.utid"
46+
},
47+
"uid.utid-login.microsoftonline.com-refreshtoken-1--": {
48+
"environment": "login.microsoftonline.com",
49+
"credential_type": "RefreshToken",
50+
"secret": "a refresh token",
51+
"client_id": "mock_client_id",
52+
"home_account_id": "uid.utid",
53+
"familyId": "1"
54+
}
55+
},
56+
"IdToken": {
57+
"uid.utid-login.microsoftonline.com-idtoken-mock_client_id-microsoft-": {
58+
"realm": "microsoft",
59+
"environment": "login.microsoftonline.com",
60+
"credential_type": "IdToken",
61+
"secret": "header.eyJvaWQiOiAib2JqZWN0MTIzNCIsICJwcmVmZXJyZWRfdXNlcm5hbWUiOiAiSm9obiBEb2UiLCAic3ViIjogInN1YiJ9.signature",
62+
"client_id": "mock_client_id",
63+
"home_account_id": "uid.utid"
64+
}
65+
},
66+
"AppMetadata": {
67+
"appmetadata-login.microsoftonline.com-mock_client_id": {
68+
"environment": "login.microsoftonline.com",
69+
"family_id": "1",
70+
"client_id": "mock_client_id"
71+
}
72+
}
73+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License.
4+
*/
5+
const express = require("express");
6+
const msal = require('@azure/msal-node');
7+
const { promises: fs } = require("fs");
8+
9+
const SERVER_PORT = process.env.PORT || 3000;
10+
11+
const readFromStorage = () => {
12+
return fs.readFile("./data/cache.json", "utf-8");
13+
};
14+
15+
const writeToStorage = (getMergedState) => {
16+
return readFromStorage().then(oldFile =>{
17+
const mergedState = getMergedState(oldFile);
18+
return fs.writeFile("./data/cacheAfterWrite.json", mergedState);
19+
})
20+
};
21+
22+
const cachePlugin = {
23+
readFromStorage,
24+
writeToStorage
25+
};
26+
27+
const publicClientConfig = {
28+
auth: {
29+
clientId: "99cab759-2aab-420b-91d8-5e3d8d4f063b",
30+
authority: "https://login.microsoftonline.com/90b8faa8-cc95-460e-a618-ee770bee1759",
31+
redirectUri: "http://localhost:3000/redirect",
32+
},
33+
cache: {
34+
cachePlugin
35+
},
36+
};
37+
const pca = new msal.PublicClientApplication(publicClientConfig);
38+
const msalCacheManager = pca.getCacheManager();
39+
let accounts;
40+
41+
// Create Express App and Routes
42+
const app = express();
43+
44+
app.get('/', (req, res) => {
45+
46+
// get Accounts
47+
accounts = msalCacheManager.getAllAccounts();
48+
console.log("Accounts: ", accounts);
49+
50+
// Build silent request
51+
const silentRequest = {
52+
account: accounts[1],
53+
redirectUri: "http://localhost:3000/redirect",
54+
};
55+
56+
// get url to sign user in and consent to scopes needed for application
57+
pca.acquireTokenSilent(silentRequest)
58+
.then((response) => {
59+
console.log("\nResponse: \n:", response);
60+
res.send(200);
61+
return msalCacheManager.writeToPersistence();
62+
})
63+
.catch((error) => {
64+
console.log(error);
65+
res.status(500).send(error);
66+
});
67+
});
68+
69+
msalCacheManager.readFromPersistence().then(() => {
70+
app.listen(SERVER_PORT, () => console.log(`Msal Node Auth Code Sample app listening on port ${SERVER_PORT}!`));
71+
});
72+

0 commit comments

Comments
 (0)