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

[Identity] allowLoggingAccountIdentifiers support #20516

Merged
merged 11 commits into from
Mar 18, 2022
2 changes: 2 additions & 0 deletions sdk/identity/identity/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

### Features Added

- All of our credentials now support a new option on their constructor: `allowLoggingAccountIdentifiers`. If set to true, after a successful authentication our logging will include information specific to the authenticated account, including: the Client ID, the Tenant ID, the Object ID of the authenticated user, and if possible the User Principal Name.

### Breaking Changes

### Bugs Fixed
Expand Down

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

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

1 change: 1 addition & 0 deletions sdk/identity/identity/review/identity.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ export { TokenCredential }

// @public
export interface TokenCredentialOptions extends CommonClientOptions {
allowLoggingAccountIdentifiers?: boolean;
authorityHost?: string;
}

Expand Down
49 changes: 49 additions & 0 deletions sdk/identity/identity/src/client/identityClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
createHttpHeaders,
createPipelineRequest,
PipelineRequest,
PipelineResponse,
} from "@azure/core-rest-pipeline";
import { AbortController, AbortSignalLike } from "@azure/abort-controller";
import { AuthenticationError, AuthenticationErrorName } from "../errors";
Expand Down Expand Up @@ -75,6 +76,7 @@ export function getIdentityClientAuthorityHost(options?: TokenCredentialOptions)
*/
export class IdentityClient extends ServiceClient implements INetworkModule {
public authorityHost: string;
private allowLoggingAccountIdentifiers?: boolean;
private abortControllers: Map<string, AbortController[] | undefined>;

constructor(options?: TokenCredentialOptions) {
Expand Down Expand Up @@ -102,6 +104,7 @@ export class IdentityClient extends ServiceClient implements INetworkModule {

this.authorityHost = baseUri;
this.abortControllers = new Map();
this.allowLoggingAccountIdentifiers = options?.allowLoggingAccountIdentifiers;
}

async sendTokenRequest(
Expand All @@ -124,6 +127,8 @@ export class IdentityClient extends ServiceClient implements INetworkModule {
return null;
}

this.logIdentifiers(response);

const token = {
accessToken: {
token: parsedBody.access_token,
Expand Down Expand Up @@ -280,6 +285,9 @@ export class IdentityClient extends ServiceClient implements INetworkModule {
});

const response = await this.sendRequest(request);

this.logIdentifiers(response);

return {
body: response.bodyAsText ? JSON.parse(response.bodyAsText) : undefined,
headers: response.headers.toJSON(),
Expand All @@ -301,10 +309,51 @@ export class IdentityClient extends ServiceClient implements INetworkModule {
});

const response = await this.sendRequest(request);

this.logIdentifiers(response);

return {
body: response.bodyAsText ? JSON.parse(response.bodyAsText) : undefined,
headers: response.headers.toJSON(),
status: response.status,
};
}

/**
* If allowLoggingAccountIdentifiers was set on the constructor options
* we try to log the account identifiers by parsing the received access token.
*
* The account identifiers we try to log are:
* - `appid`: The application or Client Identifier.
* - `upn`: User Principal Name.
* - It might not be available in some authentication scenarios.
* - If it's not available, we put a placeholder: "No User Principal Name available".
* - `tid`: Tenant Identifier.
* - `oid`: Object Identifier of the authenticated user.
*/
private logIdentifiers(response: PipelineResponse): void {
if (!this.allowLoggingAccountIdentifiers || !response.bodyAsText) {
return;
}
const unavailableUpn = "No User Principal Name available";
try {
const parsed = (response as any).parsedBody || JSON.parse(response.bodyAsText);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to get any of this info from MSAL in a way that doesn't require us to parse the raw response? I'm a bit concerned that we could somehow unintentionally log the wrong sensitive data.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As long as we limit ourselves to these 4 values we should be ok to not log something too sensitive.

MSAL does allow you to retrieve this with their AccountRecord, but we don’t use MSAL in every credential.

const accessToken = parsed.access_token;
if (!accessToken) {
// Without an access token allowLoggingAccountIdentifiers isn't useful.
return;
}
const { appid, upn, tid, oid } = JSON.parse(atob(accessToken.split(".")[1]));
logger.info(
`[Authenticated account] Client ID: ${appid}. Tenant ID: ${tid}. User Principal Name: ${
upn || unavailableUpn
}. Object ID (user): ${oid}`
);
} catch (e) {
logger.warning(
"allowLoggingAccountIdentifiers was set, but we couldn't log the account information. Error:",
e.message
);
}
}
}
5 changes: 5 additions & 0 deletions sdk/identity/identity/src/msal/nodeFlows/msalNodeCommon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ export interface MsalNodeOptions extends MsalFlowOptions {
* If the property is not specified, uses a non-regional authority endpoint.
*/
regionalAuthority?: string;
/**
* Allows logging account information once the authentication flow succeeds.
*/
allowLoggingAccountIdentifiers?: boolean;
}

/**
Expand Down Expand Up @@ -119,6 +123,7 @@ export abstract class MsalNode extends MsalBaseUtilities implements MsalFlow {
this.identityClient = new IdentityClient({
...options.tokenCredentialOptions,
authorityHost: authority,
allowLoggingAccountIdentifiers: options.allowLoggingAccountIdentifiers,
});

let clientCapabilities: string[] = ["cp1"];
Expand Down
4 changes: 4 additions & 0 deletions sdk/identity/identity/src/tokenCredentialOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,8 @@ export interface TokenCredentialOptions extends CommonClientOptions {
* The default is "https://login.microsoftonline.com".
*/
authorityHost?: string;
/**
* Allows logging account information once the authentication flow succeeds.
*/
allowLoggingAccountIdentifiers?: boolean;
}
Loading