/
authorizationCodeCredential.ts
206 lines (194 loc) · 8.34 KB
/
authorizationCodeCredential.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import qs from "qs";
import { createSpan } from "../util/tracing";
import { AuthenticationErrorName } from "../client/errors";
import { TokenCredential, GetTokenOptions, AccessToken } from "@azure/core-http";
import { IdentityClient, TokenResponse, TokenCredentialOptions } from "../client/identityClient";
import { CanonicalCode } from "@opentelemetry/api";
import { credentialLogger, formatSuccess, formatError } from "../util/logging";
import { getIdentityTokenEndpointSuffix } from "../util/identityTokenEndpoint";
import { checkTenantId } from "../util/checkTenantId";
const logger = credentialLogger("AuthorizationCodeCredential");
/**
* Enables authentication to Azure Active Directory using an authorization code
* that was obtained through the authorization code flow, described in more detail
* in the Azure Active Directory documentation:
*
* https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow
*/
export class AuthorizationCodeCredential implements TokenCredential {
private identityClient: IdentityClient;
private tenantId: string;
private clientId: string;
private clientSecret: string | undefined;
private authorizationCode: string;
private redirectUri: string;
private lastTokenResponse: TokenResponse | null = null;
/**
* Creates an instance of CodeFlowCredential with the details needed
* to request an access token using an authentication that was obtained
* from Azure Active Directory.
*
* It is currently necessary for the user of this credential to initiate
* the authorization code flow to obtain an authorization code to be used
* with this credential. A full example of this flow is provided here:
*
* https://github.com/Azure/azure-sdk-for-js/blob/master/sdk/identity/identity/samples/manual/authorizationCodeSample.ts
*
* @param tenantId - The Azure Active Directory tenant (directory) ID or name.
* 'common' may be used when dealing with multi-tenant scenarios.
* @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 authorizationCode - An authorization code that was received from following the
authorization code flow. This authorization code must not
have already been used to obtain an access token.
* @param redirectUri - The redirect URI that was used to request the authorization code.
Must be the same URI that is configured for the App Registration.
* @param options - Options for configuring the client which makes the access token request.
*/
constructor(
tenantId: string | "common",
clientId: string,
clientSecret: string,
authorizationCode: string,
redirectUri: string,
options?: TokenCredentialOptions
);
/**
* Creates an instance of CodeFlowCredential with the details needed
* to request an access token using an authentication that was obtained
* from Azure Active Directory.
*
* It is currently necessary for the user of this credential to initiate
* the authorization code flow to obtain an authorization code to be used
* with this credential. A full example of this flow is provided here:
*
* https://github.com/Azure/azure-sdk-for-js/blob/master/sdk/identity/identity/samples/manual/authorizationCodeSample.ts
*
* @param tenantId - The Azure Active Directory tenant (directory) ID or name.
* 'common' may be used when dealing with multi-tenant scenarios.
* @param clientId - The client (application) ID of an App Registration in the tenant.
* @param authorizationCode - An authorization code that was received from following the
authorization code flow. This authorization code must not
have already been used to obtain an access token.
* @param redirectUri - The redirect URI that was used to request the authorization code.
Must be the same URI that is configured for the App Registration.
* @param options - Options for configuring the client which makes the access token request.
*/
constructor(
tenantId: string | "common",
clientId: string,
authorizationCode: string,
redirectUri: string,
options?: TokenCredentialOptions
);
/**
* @hidden
* @internal
*/
constructor(
tenantId: string | "common",
clientId: string,
clientSecretOrAuthorizationCode: string,
authorizationCodeOrRedirectUri: string,
redirectUriOrOptions: string | TokenCredentialOptions | undefined,
options?: TokenCredentialOptions
) {
checkTenantId(logger, tenantId);
this.clientId = clientId;
this.tenantId = tenantId;
if (typeof redirectUriOrOptions === "string") {
// the clientId+clientSecret constructor
this.clientSecret = clientSecretOrAuthorizationCode;
this.authorizationCode = authorizationCodeOrRedirectUri;
this.redirectUri = redirectUriOrOptions;
// options okay
} else {
// clientId only
this.clientSecret = undefined;
this.authorizationCode = clientSecretOrAuthorizationCode;
this.redirectUri = authorizationCodeOrRedirectUri as string;
options = redirectUriOrOptions as TokenCredentialOptions;
}
this.identityClient = new IdentityClient(options);
}
/**
* Authenticates with Azure Active Directory and returns an access token if
* successful. If authentication cannot be performed at this time, this method may
* return null. If an error occurs during authentication, an {@link AuthenticationError}
* containing failure details will be thrown.
*
* @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.
*/
public async getToken(
scopes: string | string[],
options?: GetTokenOptions
): Promise<AccessToken | null> {
const { span, options: newOptions } = createSpan(
"AuthorizationCodeCredential-getToken",
options
);
try {
let tokenResponse: TokenResponse | null = null;
let scopeString = typeof scopes === "string" ? scopes : scopes.join(" ");
if (scopeString.indexOf("offline_access") < 0) {
scopeString += " offline_access";
}
// Try to use the refresh token first
if (this.lastTokenResponse && this.lastTokenResponse.refreshToken) {
tokenResponse = await this.identityClient.refreshAccessToken(
this.tenantId,
this.clientId,
scopeString,
this.lastTokenResponse.refreshToken,
this.clientSecret,
undefined,
newOptions
);
}
if (tokenResponse === null) {
const urlSuffix = getIdentityTokenEndpointSuffix(this.tenantId);
const webResource = this.identityClient.createWebResource({
url: `${this.identityClient.authorityHost}/${this.tenantId}/${urlSuffix}`,
method: "POST",
disableJsonStringifyOnBody: true,
deserializationMapper: undefined,
body: qs.stringify({
client_id: this.clientId,
grant_type: "authorization_code",
scope: scopeString,
code: this.authorizationCode,
redirect_uri: this.redirectUri,
client_secret: this.clientSecret
}),
headers: {
Accept: "application/json",
"Content-Type": "application/x-www-form-urlencoded"
},
abortSignal: options && options.abortSignal,
spanOptions: newOptions.tracingOptions && newOptions.tracingOptions.spanOptions
});
tokenResponse = await this.identityClient.sendTokenRequest(webResource);
}
this.lastTokenResponse = tokenResponse;
logger.getToken.info(formatSuccess(scopes));
return (tokenResponse && tokenResponse.accessToken) || null;
} catch (err) {
const code =
err.name === AuthenticationErrorName
? CanonicalCode.UNAUTHENTICATED
: CanonicalCode.UNKNOWN;
span.setStatus({
code,
message: err.message
});
logger.getToken.info(formatError(scopes, err));
throw err;
} finally {
span.end();
}
}
}