Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose idTokenClaims on AccountInfo #2554

Merged
merged 14 commits into from
Dec 2, 2020
Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"type": "patch",
"comment": "Expose idTokenClaims on AccountInfo (#2554)",
"packageName": "@azure/msal-browser",
"email": "janutter@microsoft.com",
"dependentChangeType": "patch",
"date": "2020-12-02T01:41:34.237Z"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"type": "patch",
"comment": "Expose idTokenClaims on AccountInfo (#2554)",
"packageName": "@azure/msal-common",
"email": "janutter@microsoft.com",
"dependentChangeType": "patch",
"date": "2020-12-02T01:42:04.581Z"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"type": "prerelease",
"comment": "Expose idTokenClaims on AccountInfo (#2554)",
"packageName": "@azure/msal-node",
"email": "janutter@microsoft.com",
"dependentChangeType": "patch",
"date": "2020-12-02T01:42:10.514Z"
}
6 changes: 1 addition & 5 deletions lib/msal-browser/src/cache/BrowserCacheManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,15 @@ export class BrowserCacheManager extends CacheManager {
// Window storage object (either local or sessionStorage)
private browserStorage: IWindowStorage;
// Client id of application. Used in cache keys to partition cache correctly in the case of multiple instances of MSAL.
private clientId: string;
private cryptoImpl: CryptoOps;
private logger: Logger;

// Cookie life calculation (hours * minutes * seconds * ms)
private readonly COOKIE_LIFE_MULTIPLIER = 24 * 60 * 60 * 1000;

constructor(clientId: string, cacheConfig: CacheOptions, cryptoImpl: CryptoOps, logger: Logger) {
super();
super(clientId, cryptoImpl);

this.cacheConfig = cacheConfig;
this.clientId = clientId;
this.cryptoImpl = cryptoImpl;
this.logger = logger;

this.browserStorage = this.setupBrowserStorage(cacheConfig.cacheLocation);
Expand Down
6 changes: 4 additions & 2 deletions lib/msal-browser/test/app/PublicClientApplication.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1870,7 +1870,8 @@ describe("PublicClientApplication.ts Class Unit Tests", () => {
tenantId: TEST_DATA_CLIENT_INFO.TEST_UTID,
username: "example@microsoft.com",
name: "Abe Lincoln",
localAccountId: TEST_CONFIG.OID
localAccountId: TEST_CONFIG.OID,
idTokenClaims: undefined
};

const testAccount1: AccountEntity = new AccountEntity();
Expand All @@ -1890,7 +1891,8 @@ describe("PublicClientApplication.ts Class Unit Tests", () => {
tenantId: TEST_DATA_CLIENT_INFO.TEST_UTID,
username: "anotherExample@microsoft.com",
name: "Abe Lincoln",
localAccountId: TEST_CONFIG.OID
localAccountId: TEST_CONFIG.OID,
idTokenClaims: undefined
};

const testAccount2: AccountEntity = new AccountEntity();
Expand Down
3 changes: 3 additions & 0 deletions lib/msal-common/src/account/AccountInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
* - username - preferred_username claim of the id_token that represents this account
* - localAccountId - Local, tenant-specific account identifer for this account object, usually used in legacy cases
* - name - Full name for the account, including given name and family name
* - idTokenClaims - Object contains claims from ID token
* - localAccountId - The user's account ID
*/
export type AccountInfo = {
homeAccountId: string;
Expand All @@ -19,4 +21,5 @@ export type AccountInfo = {
username: string;
localAccountId: string;
name?: string;
idTokenClaims?: object;
sameerag marked this conversation as resolved.
Show resolved Hide resolved
};
25 changes: 22 additions & 3 deletions lib/msal-common/src/cache/CacheManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,20 @@ import { TrustedAuthority } from "../authority/TrustedAuthority";
import { AppMetadataEntity } from "./entities/AppMetadataEntity";
import { ServerTelemetryEntity } from "./entities/ServerTelemetryEntity";
import { ThrottlingEntity } from "./entities/ThrottlingEntity";
import { AuthToken } from "../account/AuthToken";
import { ICrypto } from "../crypto/ICrypto";

/**
* Interface class which implement cache storage functions used by MSAL to perform validity checks, and store tokens.
*/
export abstract class CacheManager implements ICacheManager {
protected clientId: string;
protected cryptoImpl: ICrypto;

constructor(clientId: string, cryptoImpl: ICrypto) {
jasonnutter marked this conversation as resolved.
Show resolved Hide resolved
this.clientId = clientId;
this.cryptoImpl = cryptoImpl;
}

/**
* fetch the account entity from the platform cache
Expand Down Expand Up @@ -145,9 +154,15 @@ export abstract class CacheManager implements ICacheManager {
return [];
} else {
const allAccounts = accountValues.map<AccountInfo>((value) => {
let accountObj: AccountEntity = new AccountEntity();
accountObj = CacheManager.toObject(accountObj, value) as AccountEntity;
return accountObj.getAccountInfo();
const accountEntity = CacheManager.toObject<AccountEntity>(new AccountEntity(), value);
const accountInfo = accountEntity.getAccountInfo();
const idToken = this.readIdTokenFromCache(this.clientId, accountInfo);
if (idToken && !accountInfo.idTokenClaims) {
accountInfo.idTokenClaims = new AuthToken(idToken.secret, this.cryptoImpl).claims;
jasonnutter marked this conversation as resolved.
Show resolved Hide resolved
}

return accountInfo;

});
return allAccounts;
}
Expand Down Expand Up @@ -517,6 +532,10 @@ export abstract class CacheManager implements ICacheManager {
const cachedRefreshToken = this.readRefreshTokenFromCache(clientId, account, false);
const cachedAppMetadata = this.readAppMetadataFromCache(environment, clientId);

if (cachedAccount && cachedIdToken) {
cachedAccount.idTokenClaims = new AuthToken(cachedIdToken.secret, this.cryptoImpl).claims;
}

return {
account: cachedAccount,
idToken: cachedIdToken,
Expand Down
11 changes: 9 additions & 2 deletions lib/msal-common/src/cache/entities/AccountEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { AccountInfo } from "../../account/AccountInfo";
import { ClientAuthError } from "../../error/ClientAuthError";
import { AuthorityType } from "../../authority/AuthorityType";
import { Logger } from "../../logger/Logger";
import { TokenClaims } from "../../account/TokenClaims";

/**
* Type that defines required and optional parameters for an Account field (based on universal cache schema implemented by all MSALs).
Expand All @@ -39,6 +40,7 @@ import { Logger } from "../../logger/Logger";
* lastModificationTime: last time this entity was modified in the cache
* lastModificationApp:
* oboAssertion: access token passed in as part of OBO request
* idTokenClaims: Object containing claims parsed from ID token
* }
*/
export class AccountEntity {
Expand All @@ -53,6 +55,7 @@ export class AccountEntity {
lastModificationTime?: string;
lastModificationApp?: string;
oboAssertion?: string;
idTokenClaims?: TokenClaims;

/**
* Generate Account Id key component as per the schema: <home_account_id>-<environment>
Expand Down Expand Up @@ -105,6 +108,7 @@ export class AccountEntity {
username: this.username,
localAccountId: this.localAccountId,
name: this.name,
idTokenClaims: this.idTokenClaims
};
}

Expand Down Expand Up @@ -151,8 +155,10 @@ export class AccountEntity {
// non AAD scenarios can have empty realm
account.realm = idToken?.claims?.tid || "";
account.oboAssertion = oboAssertion;

if (idToken) {
account.idTokenClaims = idToken.claims;

// How do you account for MSA CID here?
account.localAccountId = idToken?.claims?.oid || idToken?.claims?.sub || "";

Expand Down Expand Up @@ -198,10 +204,11 @@ export class AccountEntity {
// upn claim for most ADFS scenarios
account.username = idToken?.claims?.upn || "";
account.name = idToken?.claims?.name || "";
account.idTokenClaims = idToken?.claims;
}

account.environment = env;

/*
* add uniqueName to claims
* account.name = idToken.claims.uniqueName;
Expand Down
2 changes: 1 addition & 1 deletion lib/msal-common/src/config/ClientConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ export function buildClientConfiguration(
authOptions: buildAuthOptions(userAuthOptions),
systemOptions: { ...DEFAULT_SYSTEM_OPTIONS, ...userSystemOptions },
loggerOptions: { ...DEFAULT_LOGGER_IMPLEMENTATION, ...userLoggerOption },
storageInterface: storageImplementation || new DefaultStorageClass(),
storageInterface: storageImplementation || new DefaultStorageClass(userAuthOptions.clientId, cryptoImplementation || DEFAULT_CRYPTO_IMPLEMENTATION),
networkInterface: networkImplementation || DEFAULT_NETWORK_IMPLEMENTATION,
cryptoInterface: cryptoImplementation || DEFAULT_CRYPTO_IMPLEMENTATION,
clientCredentials: clientCredentials || DEFAULT_CLIENT_CREDENTIALS,
Expand Down
7 changes: 7 additions & 0 deletions lib/msal-common/test/cache/entities/AccountEntity.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ describe("AccountEntity.ts Unit Tests", () => {
expect(acc.realm).to.eq(idTokenClaims.tid);
expect(acc.username).to.eq("AbeLi@microsoft.com");
expect(acc.localAccountId).to.eql(idTokenClaims.oid);
expect(acc.idTokenClaims).to.eq(idTokenClaims);
});

it("create an Account with sub instead of oid as localAccountId", () => {
Expand Down Expand Up @@ -191,6 +192,7 @@ describe("AccountEntity.ts Unit Tests", () => {
expect(acc.realm).to.eq(idTokenClaims.tid);
expect(acc.username).to.eq("AbeLi@microsoft.com");
expect(acc.localAccountId).to.eql(idTokenClaims.sub);
expect(acc.idTokenClaims).to.eq(idTokenClaims);
});

it("create an Account with emails claim instead of preferred_username claim", () => {
Expand Down Expand Up @@ -229,6 +231,7 @@ describe("AccountEntity.ts Unit Tests", () => {
expect(acc.realm).to.eq(idTokenClaims.tid);
expect(acc.username).to.eq("AbeLi@microsoft.com");
expect(acc.localAccountId).to.eql(idTokenClaims.oid);
expect(acc.idTokenClaims).to.eq(idTokenClaims);
});

it("create an Account no preferred_username or emails claim", () => {
Expand Down Expand Up @@ -273,6 +276,7 @@ describe("AccountEntity.ts Unit Tests", () => {
expect(acc.realm).to.eq(idTokenClaims.tid);
expect(acc.username).to.eq("");
expect(acc.localAccountId).to.eql(idTokenClaims.oid);
expect(acc.idTokenClaims).to.eq(idTokenClaims);
});

it("creates a generic account", () => {
Expand Down Expand Up @@ -312,6 +316,7 @@ describe("AccountEntity.ts Unit Tests", () => {
expect(acc.localAccountId).to.eq(idTokenClaims.oid);
expect(acc.authorityType).to.eq(CacheAccountType.GENERIC_ACCOUNT_TYPE);
expect(AccountEntity.isAccountEntity(acc)).to.eql(true);
expect(acc.idTokenClaims).to.eq(idTokenClaims);
});

it("verify if an object is an account entity", () => {
Expand Down Expand Up @@ -369,6 +374,7 @@ describe("AccountEntity.ts Unit Tests for ADFS", () => {
expect(acc.localAccountId).to.eq(idTokenClaims.oid);
expect(acc.authorityType).to.eq(CacheAccountType.ADFS_ACCOUNT_TYPE);
expect(AccountEntity.isAccountEntity(acc)).to.eql(true);
expect(acc.idTokenClaims).to.eq(idTokenClaims);
});

it("creates a generic ADFS account without OID", () => {
Expand Down Expand Up @@ -407,5 +413,6 @@ describe("AccountEntity.ts Unit Tests for ADFS", () => {
expect(acc.authorityType).to.eq(CacheAccountType.ADFS_ACCOUNT_TYPE);
expect(acc.localAccountId).to.eq(idTokenClaims.sub);
expect(AccountEntity.isAccountEntity(acc)).to.eql(true);
expect(acc.idTokenClaims).to.eq(idTokenClaims);
});
});
6 changes: 4 additions & 2 deletions lib/msal-common/test/client/RefreshTokenClient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ describe("RefreshTokenClient unit tests", () => {
environment: "login.windows.net",
username: ID_TOKEN_CLAIMS.preferred_username,
name: ID_TOKEN_CLAIMS.name,
localAccountId: ID_TOKEN_CLAIMS.oid
localAccountId: ID_TOKEN_CLAIMS.oid,
idTokenClaims: ID_TOKEN_CLAIMS
};

beforeEach(async () => {
Expand Down Expand Up @@ -179,7 +180,8 @@ describe("RefreshTokenClient unit tests", () => {
environment: "login.windows.net",
username: ID_TOKEN_CLAIMS.preferred_username,
name: ID_TOKEN_CLAIMS.name,
localAccountId: ID_TOKEN_CLAIMS.oid
localAccountId: ID_TOKEN_CLAIMS.oid,
idTokenClaims: ID_TOKEN_CLAIMS
};

beforeEach(async () => {
Expand Down
3 changes: 2 additions & 1 deletion lib/msal-common/test/client/SilentFlowClient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,8 @@ describe("SilentFlowClient unit tests", () => {
environment: "login.windows.net",
username: ID_TOKEN_CLAIMS.preferred_username,
name: ID_TOKEN_CLAIMS.name,
localAccountId: ID_TOKEN_CLAIMS.oid
localAccountId: ID_TOKEN_CLAIMS.oid,
idTokenClaims: ID_TOKEN_CLAIMS
};

beforeEach(async () => {
Expand Down
7 changes: 4 additions & 3 deletions lib/msal-node/src/cache/Storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import {
ThrottlingEntity,
CacheManager,
Logger,
ValidCacheType
ValidCacheType,
ICrypto
} from "@azure/msal-common";
import { Deserializer } from "./serializer/Deserializer";
import { Serializer } from "./serializer/Serializer";
Expand All @@ -28,8 +29,8 @@ export class Storage extends CacheManager {
private cache: CacheKVStore = {};
private changeEmitters: Array<Function> = [];

constructor(logger: Logger) {
super();
constructor(logger: Logger, clientId: string, cryptoImpl: ICrypto) {
super(clientId, cryptoImpl);
this.logger = logger;
}

Expand Down
4 changes: 2 additions & 2 deletions lib/msal-node/src/client/ClientApplication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,14 @@ export abstract class ClientApplication {
*/
protected constructor(configuration: Configuration) {
this.config = buildAppConfiguration(configuration);
this.cryptoProvider = new CryptoProvider();
this.logger = new Logger(this.config.system!.loggerOptions!, name, version);
this.storage = new Storage(this.logger);
this.storage = new Storage(this.logger, this.config.auth.clientId, this.cryptoProvider);
this.tokenCache = new TokenCache(
this.storage,
this.logger,
this.config.cache!.cachePlugin
);
this.cryptoProvider = new CryptoProvider();
TrustedAuthority.setTrustedAuthoritiesFromConfig(this.config.auth.knownAuthorities!, this.config.auth.cloudDiscoveryMetadata!);
}

Expand Down