-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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] Simple OnBehalfOfCredential #17137
Changes from 1 commit
79fed01
8d53b1d
8910a93
f8b1d57
2555138
d1b0207
1293cf4
c925bcb
e8d7ad4
16eef26
22e9cca
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT license. | ||
|
||
import { TokenCredential, AccessToken } from "@azure/core-auth"; | ||
|
||
import { credentialLogger, formatError } from "../util/logging"; | ||
|
||
const BrowserNotSupportedError = new Error("OnBehalfOfCredential is not supported in the browser."); | ||
const logger = credentialLogger("OnBehalfOfCredential"); | ||
|
||
export class OnBehalfOfCredential implements TokenCredential { | ||
constructor() { | ||
logger.info(formatError("", BrowserNotSupportedError)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's up with the empty string? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This empty string is just the scope. Generally, the use cases of this method pass a scope through, and the scope is far smaller than the message. Moving the scope after the message looks awkward for this specific internal utility, IMO |
||
throw BrowserNotSupportedError; | ||
} | ||
|
||
public getToken(): Promise<AccessToken | null> { | ||
logger.getToken.info(formatError("", BrowserNotSupportedError)); | ||
throw BrowserNotSupportedError; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT license. | ||
|
||
import { AccessToken, GetTokenOptions, TokenCredential } from "@azure/core-auth"; | ||
|
||
import { MsalOnBehalfOf } from "../msal/nodeFlows/msalOnBehalfOf"; | ||
import { credentialLogger } from "../util/logging"; | ||
import { trace } from "../util/tracing"; | ||
import { MsalFlow } from "../msal/flows"; | ||
import { OnBehalfOfCredentialOptions } from "./onBehalfOfCredentialOptions"; | ||
|
||
const logger = credentialLogger("OnBehalfOfCredential"); | ||
|
||
/** | ||
* Enables authentication to Azure Active Directory using the [On Behalf Of flow](https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-on-behalf-of-flow). | ||
*/ | ||
export class OnBehalfOfCredential implements TokenCredential { | ||
private msalFlow: MsalFlow; | ||
|
||
/** | ||
* Creates an instance of the {@link OnBehalfOfCredential} with the details | ||
* needed to authenticate against Azure Active Directory with a client | ||
* secret, and an user assertion. | ||
* | ||
* Example using the `KeyClient` from [\@azure/keyvault-keys](https://www.npmjs.com/package/\@azure/keyvault-keys): | ||
* | ||
* ```ts | ||
* const tokenCredential = new OnBehalfOfCredential(tenantId, clientId, clientSecret, "access-token"); | ||
* const client = new KeyClient("vault-url", tokenCredential); | ||
* | ||
* await client.getKey("key-name", { authenticationOptions: { userAssertion } }); | ||
sadasant marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* ``` | ||
* | ||
* @param tenantId - The Azure Active Directory tenant (directory) ID. | ||
* @param clientId - The client (application) ID of an App Registration in the tenant. | ||
* @param clientSecret - A client secret that was generated for the App Registration. | ||
* @param options - Options for configuring the client which makes the authentication request. | ||
*/ | ||
constructor( | ||
private tenantId: string, | ||
private clientId: string, | ||
private clientSecret: string, | ||
sadasant marked this conversation as resolved.
Show resolved
Hide resolved
|
||
private userAssertionToken: string, | ||
private options: OnBehalfOfCredentialOptions = {} | ||
) { | ||
this.msalFlow = new MsalOnBehalfOf({ | ||
...this.options, | ||
logger, | ||
clientId: this.clientId, | ||
tenantId: this.tenantId, | ||
clientSecret: this.clientSecret, | ||
userAssertionToken: this.userAssertionToken, | ||
tokenCredentialOptions: this.options | ||
}); | ||
} | ||
sadasant marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/** | ||
* Authenticates with Azure Active Directory and returns an access token if successful. | ||
* If authentication fails, a {@link CredentialUnavailableError} will be thrown with the details of the failure. | ||
* | ||
* @param scopes - The list of scopes for which the token will have access. | ||
* @param options - The options used to configure any requests this | ||
* TokenCredential implementation might make. | ||
*/ | ||
async getToken(scopes: string | string[], options: GetTokenOptions = {}): Promise<AccessToken> { | ||
return trace(`${this.constructor.name}.getToken`, options, async (newOptions) => { | ||
const arrayScopes = Array.isArray(scopes) ? scopes : [scopes]; | ||
return this.msalFlow.getToken(arrayScopes, newOptions); | ||
}); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT license. | ||
|
||
import { TokenCredentialOptions } from "../client/identityClient"; | ||
import { CredentialPersistenceOptions } from "./credentialPersistenceOptions"; | ||
|
||
/** | ||
* Optional parameters for the {@link OnBehalfOfCredential} class. | ||
*/ | ||
export interface OnBehalfOfCredentialOptions | ||
extends TokenCredentialOptions, | ||
CredentialPersistenceOptions {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the MIT license. | ||
|
||
import { AccessToken } from "@azure/core-auth"; | ||
|
||
import { CredentialFlowGetTokenOptions } from "../credentials"; | ||
import { MsalNodeOptions, MsalNode } from "./nodeCommon"; | ||
|
||
/** | ||
* Options that can be passed to configure MSAL to handle On-Behalf-Of authentication requests. | ||
* @internal | ||
*/ | ||
export interface MSALOnBehalfOfOptions extends MsalNodeOptions { | ||
clientSecret: string; | ||
userAssertionToken: string; | ||
} | ||
|
||
/** | ||
* MSAL on behalf of flow. Calls to MSAL's confidential application's `acquireTokenOnBehalfOf` during `doGetToken`. | ||
* @internal | ||
*/ | ||
export class MsalOnBehalfOf extends MsalNode { | ||
private userAssertionToken: string; | ||
|
||
constructor(options: MSALOnBehalfOfOptions) { | ||
super(options); | ||
this.logger.info("Initialized MSAL's On-Behalf-Of flow"); | ||
this.requiresConfidential = true; | ||
this.msalConfig.auth.clientSecret = options.clientSecret; | ||
this.userAssertionToken = options.userAssertionToken; | ||
} | ||
|
||
protected async doGetToken( | ||
scopes: string[], | ||
options: CredentialFlowGetTokenOptions = {} | ||
): Promise<AccessToken> { | ||
try { | ||
const result = await this.confidentialApp!.acquireTokenOnBehalfOf({ | ||
scopes, | ||
correlationId: options.correlationId, | ||
authority: options.authority, | ||
oboAssertion: this.userAssertionToken | ||
}); | ||
return this.handleResult(scopes, this.clientId, result || undefined); | ||
} catch (err) { | ||
throw this.handleError(scopes, err, options); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aren't ClientSecret and ClientCert at least functional in browsers (even if that use case isn't supported?). Should OBO work in browsers as well if CSC does?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Client Certificate Credential definitely doesn’t work in browsers: https://github.com/Azure/azure-sdk-for-js/blob/44712109c705d23d5ec52d98a3a508157674df84/sdk/identity/identity/src/credentials/clientCertificateCredential.browser.ts