@azure/identity 1.0.0-preview.1 → 1.0.0-preview.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @azure/identity might be problematic. Click here for more details.
- package/CHANGELOG.md +24 -0
- package/README.md +50 -23
- package/browser/identity.js +9828 -0
- package/browser/identity.js.map +1 -0
- package/browser/identity.min.js +2 -0
- package/browser/identity.min.js.map +1 -0
- package/dist/index.js +475 -205
- package/dist/index.js.map +1 -1
- package/dist-esm/src/client/errors.d.ts +1 -1
- package/dist-esm/src/client/errors.d.ts.map +1 -1
- package/dist-esm/src/client/errors.js +9 -1
- package/dist-esm/src/client/errors.js.map +1 -1
- package/dist-esm/src/client/identityClient.d.ts +20 -17
- package/dist-esm/src/client/identityClient.d.ts.map +1 -1
- package/dist-esm/src/client/identityClient.js +42 -206
- package/dist-esm/src/client/identityClient.js.map +1 -1
- package/dist-esm/src/credentials/clientCertificateCredential.browser.d.ts +7 -0
- package/dist-esm/src/credentials/clientCertificateCredential.browser.d.ts.map +1 -0
- package/dist-esm/src/credentials/clientCertificateCredential.browser.js +12 -0
- package/dist-esm/src/credentials/clientCertificateCredential.browser.js.map +1 -0
- package/dist-esm/src/credentials/clientCertificateCredential.d.ts +5 -5
- package/dist-esm/src/credentials/clientCertificateCredential.d.ts.map +1 -1
- package/dist-esm/src/credentials/clientCertificateCredential.js +59 -5
- package/dist-esm/src/credentials/clientCertificateCredential.js.map +1 -1
- package/dist-esm/src/credentials/clientSecretCredential.d.ts +3 -3
- package/dist-esm/src/credentials/clientSecretCredential.d.ts.map +1 -1
- package/dist-esm/src/credentials/clientSecretCredential.js +27 -4
- package/dist-esm/src/credentials/clientSecretCredential.js.map +1 -1
- package/dist-esm/src/credentials/deviceCodeCredential.browser.d.ts +7 -0
- package/dist-esm/src/credentials/deviceCodeCredential.browser.d.ts.map +1 -0
- package/dist-esm/src/credentials/deviceCodeCredential.browser.js +12 -0
- package/dist-esm/src/credentials/deviceCodeCredential.browser.js.map +1 -0
- package/dist-esm/src/credentials/deviceCodeCredential.d.ts +67 -0
- package/dist-esm/src/credentials/deviceCodeCredential.d.ts.map +1 -0
- package/dist-esm/src/credentials/deviceCodeCredential.js +139 -0
- package/dist-esm/src/credentials/deviceCodeCredential.js.map +1 -0
- package/dist-esm/src/credentials/environmentCredential.browser.d.ts +7 -0
- package/dist-esm/src/credentials/environmentCredential.browser.d.ts.map +1 -0
- package/dist-esm/src/credentials/environmentCredential.browser.js +12 -0
- package/dist-esm/src/credentials/environmentCredential.browser.js.map +1 -0
- package/dist-esm/src/credentials/environmentCredential.d.ts.map +1 -1
- package/dist-esm/src/credentials/environmentCredential.js +0 -4
- package/dist-esm/src/credentials/environmentCredential.js.map +1 -1
- package/dist-esm/src/credentials/interactiveBrowserCredential.browser.d.ts +32 -0
- package/dist-esm/src/credentials/interactiveBrowserCredential.browser.d.ts.map +1 -0
- package/dist-esm/src/credentials/interactiveBrowserCredential.browser.js +112 -0
- package/dist-esm/src/credentials/interactiveBrowserCredential.browser.js.map +1 -0
- package/dist-esm/src/credentials/interactiveBrowserCredential.d.ts +12 -0
- package/dist-esm/src/credentials/interactiveBrowserCredential.d.ts.map +1 -0
- package/dist-esm/src/credentials/interactiveBrowserCredential.js +17 -0
- package/dist-esm/src/credentials/interactiveBrowserCredential.js.map +1 -0
- package/dist-esm/src/credentials/interactiveBrowserCredentialOptions.d.ts +24 -0
- package/dist-esm/src/credentials/interactiveBrowserCredentialOptions.d.ts.map +1 -0
- package/dist-esm/src/credentials/interactiveBrowserCredentialOptions.js +3 -0
- package/dist-esm/src/credentials/interactiveBrowserCredentialOptions.js.map +1 -0
- package/dist-esm/src/credentials/managedIdentityCredential.browser.d.ts +7 -0
- package/dist-esm/src/credentials/managedIdentityCredential.browser.d.ts.map +1 -0
- package/dist-esm/src/credentials/managedIdentityCredential.browser.js +15 -0
- package/dist-esm/src/credentials/managedIdentityCredential.browser.js.map +1 -0
- package/dist-esm/src/credentials/managedIdentityCredential.d.ts +10 -1
- package/dist-esm/src/credentials/managedIdentityCredential.d.ts.map +1 -1
- package/dist-esm/src/credentials/managedIdentityCredential.js +144 -2
- package/dist-esm/src/credentials/managedIdentityCredential.js.map +1 -1
- package/dist-esm/src/credentials/usernamePasswordCredential.d.ts +39 -0
- package/dist-esm/src/credentials/usernamePasswordCredential.d.ts.map +1 -0
- package/dist-esm/src/credentials/usernamePasswordCredential.js +67 -0
- package/dist-esm/src/credentials/usernamePasswordCredential.js.map +1 -0
- package/dist-esm/src/index.d.ts +4 -0
- package/dist-esm/src/index.d.ts.map +1 -1
- package/dist-esm/src/index.js +3 -0
- package/dist-esm/src/index.js.map +1 -1
- package/package.json +32 -14
- package/src/client/errors.ts +11 -3
- package/src/client/identityClient.ts +64 -246
- package/src/credentials/clientCertificateCredential.browser.ts +27 -0
- package/src/credentials/clientCertificateCredential.ts +72 -22
- package/src/credentials/clientSecretCredential.ts +32 -17
- package/src/credentials/deviceCodeCredential.browser.ts +27 -0
- package/src/credentials/deviceCodeCredential.ts +203 -0
- package/src/credentials/environmentCredential.browser.ts +19 -0
- package/src/credentials/environmentCredential.ts +5 -9
- package/src/credentials/interactiveBrowserCredential.browser.ts +134 -0
- package/src/credentials/interactiveBrowserCredential.ts +31 -0
- package/src/credentials/interactiveBrowserCredentialOptions.ts +30 -0
- package/src/credentials/managedIdentityCredential.browser.ts +22 -0
- package/src/credentials/managedIdentityCredential.ts +179 -8
- package/src/credentials/usernamePasswordCredential.ts +83 -0
- package/src/index.ts +4 -0
|
@@ -2,44 +2,58 @@
|
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
3
|
|
|
4
4
|
import qs from "qs";
|
|
5
|
-
import jws from "jws";
|
|
6
|
-
import uuid from "uuid";
|
|
7
5
|
import {
|
|
8
6
|
AccessToken,
|
|
9
7
|
ServiceClient,
|
|
10
8
|
ServiceClientOptions,
|
|
11
|
-
GetTokenOptions,
|
|
12
9
|
WebResource,
|
|
13
10
|
RequestPrepareOptions,
|
|
14
|
-
|
|
11
|
+
GetTokenOptions
|
|
15
12
|
} from "@azure/core-http";
|
|
16
13
|
import { AuthenticationError } from "./errors";
|
|
17
14
|
|
|
18
|
-
const SelfSignedJwtLifetimeMins = 10;
|
|
19
15
|
const DefaultAuthorityHost = "https://login.microsoftonline.com";
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* An internal type used to communicate details of a token request's
|
|
19
|
+
* response that should not be sent back as part of the AccessToken.
|
|
20
|
+
*/
|
|
21
|
+
export interface TokenResponse {
|
|
22
|
+
/**
|
|
23
|
+
* The AccessToken to be returned from getToken.
|
|
24
|
+
*/
|
|
25
|
+
accessToken: AccessToken,
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The refresh token if the 'offline_access' scope was used.
|
|
29
|
+
*/
|
|
30
|
+
refreshToken?: string
|
|
31
|
+
}
|
|
24
32
|
|
|
25
33
|
export class IdentityClient extends ServiceClient {
|
|
34
|
+
public authorityHost: string;
|
|
35
|
+
|
|
26
36
|
constructor(options?: IdentityClientOptions) {
|
|
27
37
|
options = options || IdentityClient.getDefaultOptions();
|
|
28
38
|
super(undefined, options);
|
|
29
39
|
|
|
30
|
-
this.baseUri = options.authorityHost;
|
|
40
|
+
this.baseUri = this.authorityHost = options.authorityHost || DefaultAuthorityHost;
|
|
41
|
+
|
|
42
|
+
if (!this.baseUri.startsWith("https:")) {
|
|
43
|
+
throw new Error("The authorityHost address must use the 'https' protocol.");
|
|
44
|
+
}
|
|
31
45
|
}
|
|
32
46
|
|
|
33
|
-
|
|
47
|
+
createWebResource(requestOptions: RequestPrepareOptions): WebResource {
|
|
34
48
|
const webResource = new WebResource();
|
|
35
49
|
webResource.prepare(requestOptions);
|
|
36
50
|
return webResource;
|
|
37
51
|
}
|
|
38
52
|
|
|
39
|
-
|
|
53
|
+
async sendTokenRequest(
|
|
40
54
|
webResource: WebResource,
|
|
41
55
|
expiresOnParser?: (responseBody: any) => number,
|
|
42
|
-
): Promise<
|
|
56
|
+
): Promise<TokenResponse | null> {
|
|
43
57
|
const response = await this.sendRequest(webResource);
|
|
44
58
|
|
|
45
59
|
expiresOnParser = expiresOnParser || ((responseBody: any) => {
|
|
@@ -48,262 +62,66 @@ export class IdentityClient extends ServiceClient {
|
|
|
48
62
|
|
|
49
63
|
if (response.status === 200 || response.status === 201) {
|
|
50
64
|
return {
|
|
51
|
-
|
|
52
|
-
|
|
65
|
+
accessToken: {
|
|
66
|
+
token: response.parsedBody.access_token,
|
|
67
|
+
expiresOnTimestamp: expiresOnParser(response.parsedBody)
|
|
68
|
+
},
|
|
69
|
+
refreshToken: response.parsedBody.refresh_token,
|
|
53
70
|
};
|
|
54
71
|
} else {
|
|
55
|
-
throw new AuthenticationError(response.status, response.bodyAsText);
|
|
72
|
+
throw new AuthenticationError(response.status, response.parsedBody || response.bodyAsText);
|
|
56
73
|
}
|
|
57
74
|
}
|
|
58
75
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (!scope.endsWith(DefaultScopeSuffix)) {
|
|
72
|
-
return scope;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return scope.substr(0, scope.lastIndexOf(DefaultScopeSuffix));
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
private dateInSeconds(date: Date): number {
|
|
79
|
-
return Math.floor(date.getTime() / 1000);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
private addMinutes(date: Date, minutes: number): Date {
|
|
83
|
-
date.setMinutes(date.getMinutes() + minutes);
|
|
84
|
-
return date;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
private createImdsAuthRequest(resource: string, clientId?: string): RequestPrepareOptions {
|
|
88
|
-
const queryParameters: any = {
|
|
89
|
-
resource,
|
|
90
|
-
"api-version": ImdsApiVersion
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
if (clientId) {
|
|
94
|
-
queryParameters.client_id = clientId;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return {
|
|
98
|
-
url: ImdsEndpoint,
|
|
99
|
-
method: "GET",
|
|
100
|
-
queryParameters,
|
|
101
|
-
headers: {
|
|
102
|
-
Accept: "application/json",
|
|
103
|
-
Metadata: true
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
private createAppServiceMsiAuthRequest(resource: string, clientId?: string): RequestPrepareOptions {
|
|
109
|
-
const queryParameters: any = {
|
|
110
|
-
resource,
|
|
111
|
-
"api-version": AppServiceMsiApiVersion,
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
if (clientId) {
|
|
115
|
-
queryParameters.client_id = clientId;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return {
|
|
119
|
-
url: process.env.MSI_ENDPOINT,
|
|
120
|
-
method: "GET",
|
|
121
|
-
queryParameters,
|
|
122
|
-
headers: {
|
|
123
|
-
Accept: "application/json",
|
|
124
|
-
secret: process.env.MSI_SECRET
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
private createCloudShellMsiAuthRequest(resource: string, clientId?: string): RequestPrepareOptions {
|
|
130
|
-
const body: any = {
|
|
131
|
-
resource
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
if (clientId) {
|
|
135
|
-
body.client_id = clientId;
|
|
76
|
+
async refreshAccessToken(
|
|
77
|
+
tenantId: string,
|
|
78
|
+
clientId: string,
|
|
79
|
+
scopes: string,
|
|
80
|
+
refreshToken: string | undefined,
|
|
81
|
+
clientSecret: string | undefined,
|
|
82
|
+
expiresOnParser?: (responseBody: any) => number,
|
|
83
|
+
options?: GetTokenOptions
|
|
84
|
+
): Promise<TokenResponse | null> {
|
|
85
|
+
if (refreshToken === undefined) {
|
|
86
|
+
return null;
|
|
136
87
|
}
|
|
137
88
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
Accept: "application/json",
|
|
144
|
-
Metadata: true,
|
|
145
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
146
|
-
}
|
|
89
|
+
const refreshParams = {
|
|
90
|
+
grant_type: "refresh_token",
|
|
91
|
+
client_id: clientId,
|
|
92
|
+
refresh_token: refreshToken,
|
|
93
|
+
scope: scopes
|
|
147
94
|
};
|
|
148
|
-
}
|
|
149
95
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
// This will always be populated, but let's make TypeScript happy
|
|
154
|
-
if (request.headers) {
|
|
155
|
-
// Remove the Metadata header to invoke a request error from
|
|
156
|
-
// IMDS endpoint
|
|
157
|
-
delete request.headers.Metadata;
|
|
96
|
+
if (clientSecret !== undefined) {
|
|
97
|
+
(refreshParams as any).client_secret = clientSecret;
|
|
158
98
|
}
|
|
159
99
|
|
|
160
|
-
// Create a request with a 500 msec timeout since we expect that
|
|
161
|
-
// not having a "Metadata" header should cause an error to be
|
|
162
|
-
// returned quickly from the endpoint, proving its availability.
|
|
163
|
-
const webResource = this.createWebResource(request);
|
|
164
|
-
webResource.timeout = 500;
|
|
165
|
-
|
|
166
|
-
try {
|
|
167
|
-
await this.sendRequest(webResource);
|
|
168
|
-
} catch (err) {
|
|
169
|
-
if (err instanceof RestError && err.code === RestError.REQUEST_SEND_ERROR) {
|
|
170
|
-
// Either request failed or IMDS endpoint isn't available
|
|
171
|
-
return false;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// If we received any response, the endpoint is available
|
|
176
|
-
return true;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
authenticateClientSecret(
|
|
180
|
-
tenantId: string,
|
|
181
|
-
clientId: string,
|
|
182
|
-
clientSecret: string,
|
|
183
|
-
scopes: string | string[],
|
|
184
|
-
getTokenOptions?: GetTokenOptions
|
|
185
|
-
): Promise<AccessToken | null> {
|
|
186
100
|
const webResource = this.createWebResource({
|
|
187
|
-
url: `${this.
|
|
101
|
+
url: `${this.authorityHost}/${tenantId}/oauth2/v2.0/token`,
|
|
188
102
|
method: "POST",
|
|
189
103
|
disableJsonStringifyOnBody: true,
|
|
190
104
|
deserializationMapper: undefined,
|
|
191
|
-
body: qs.stringify(
|
|
192
|
-
response_type: "token",
|
|
193
|
-
grant_type: "client_credentials",
|
|
194
|
-
client_id: clientId,
|
|
195
|
-
client_secret: clientSecret,
|
|
196
|
-
scope: typeof scopes === "string" ? scopes : scopes.join(" ")
|
|
197
|
-
}),
|
|
105
|
+
body: qs.stringify(refreshParams),
|
|
198
106
|
headers: {
|
|
199
107
|
Accept: "application/json",
|
|
200
108
|
"Content-Type": "application/x-www-form-urlencoded"
|
|
201
109
|
},
|
|
202
|
-
abortSignal:
|
|
110
|
+
abortSignal: options && options.abortSignal
|
|
203
111
|
});
|
|
204
112
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
getTokenOptions?: GetTokenOptions
|
|
213
|
-
): Promise<AccessToken | null> {
|
|
214
|
-
let authRequestOptions: RequestPrepareOptions;
|
|
215
|
-
const resource = this.mapScopesToResource(scopes);
|
|
216
|
-
let expiresInParser: ((requestBody: any) => number) | undefined;
|
|
217
|
-
|
|
218
|
-
// Detect which type of environment we are running in
|
|
219
|
-
if (process.env.MSI_ENDPOINT) {
|
|
220
|
-
if (process.env.MSI_SECRET) {
|
|
221
|
-
// Running in App Service
|
|
222
|
-
authRequestOptions = this.createAppServiceMsiAuthRequest(resource, clientId);
|
|
223
|
-
expiresInParser = (requestBody: any) => {
|
|
224
|
-
// Parse a date format like "06/20/2019 02:57:58 +00:00" and
|
|
225
|
-
// convert it into a JavaScript-formatted date
|
|
226
|
-
const m = requestBody.expires_on.match(/(\d\d)\/(\d\d)\/(\d\d\d\d) (\d\d):(\d\d):(\d\d) (\+|-)(\d\d):(\d\d)/)
|
|
227
|
-
return Date.parse(`${m[3]}-${m[1]}-${m[2]}T${m[4]}:${m[5]}:${m[6]}${m[7]}${m[8]}:${m[9]}`)
|
|
228
|
-
};
|
|
229
|
-
} else {
|
|
230
|
-
// Running in Cloud Shell
|
|
231
|
-
authRequestOptions = this.createCloudShellMsiAuthRequest(resource, clientId);
|
|
232
|
-
}
|
|
233
|
-
} else {
|
|
234
|
-
// Ping the IMDS endpoint to see if it's available
|
|
235
|
-
if (!checkIfImdsEndpointAvailable || await this.pingImdsEndpoint(resource, clientId)) {
|
|
236
|
-
// Running in an Azure VM
|
|
237
|
-
authRequestOptions = this.createImdsAuthRequest(resource, clientId);
|
|
238
|
-
} else {
|
|
239
|
-
// Returning null tells the ManagedIdentityCredential that
|
|
240
|
-
// no MSI authentication endpoints are available
|
|
113
|
+
try {
|
|
114
|
+
return await this.sendTokenRequest(webResource, expiresOnParser);
|
|
115
|
+
} catch (err) {
|
|
116
|
+
if (err instanceof AuthenticationError && err.errorResponse.error === "interaction_required") {
|
|
117
|
+
// It's likely that the refresh token has expired, so
|
|
118
|
+
// return null so that the credential implementation will
|
|
119
|
+
// initiate the authentication flow again.
|
|
241
120
|
return null;
|
|
121
|
+
} else {
|
|
122
|
+
throw err;
|
|
242
123
|
}
|
|
243
124
|
}
|
|
244
|
-
|
|
245
|
-
const webResource = this.createWebResource({
|
|
246
|
-
disableJsonStringifyOnBody: true,
|
|
247
|
-
deserializationMapper: undefined,
|
|
248
|
-
abortSignal: getTokenOptions && getTokenOptions.abortSignal,
|
|
249
|
-
...authRequestOptions
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
return this.sendTokenRequest(webResource, expiresInParser);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
authenticateClientCertificate(
|
|
256
|
-
tenantId: string,
|
|
257
|
-
clientId: string,
|
|
258
|
-
certificateString: string,
|
|
259
|
-
certificateX5t: string,
|
|
260
|
-
scopes: string | string[],
|
|
261
|
-
getTokenOptions?: GetTokenOptions
|
|
262
|
-
): Promise<AccessToken | null> {
|
|
263
|
-
const tokenId = uuid.v4();
|
|
264
|
-
const audienceUrl = `${this.baseUri}/${tenantId}/oauth2/v2.0/token`;
|
|
265
|
-
const header: jws.Header = {
|
|
266
|
-
typ: "JWT",
|
|
267
|
-
alg: "RS256",
|
|
268
|
-
x5t: certificateX5t
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
const payload = {
|
|
272
|
-
iss: clientId,
|
|
273
|
-
sub: clientId,
|
|
274
|
-
aud: audienceUrl,
|
|
275
|
-
jti: tokenId,
|
|
276
|
-
nbf: this.dateInSeconds(new Date()),
|
|
277
|
-
exp: this.dateInSeconds(this.addMinutes(new Date(), SelfSignedJwtLifetimeMins))
|
|
278
|
-
};
|
|
279
|
-
|
|
280
|
-
const clientAssertion = jws.sign({
|
|
281
|
-
header,
|
|
282
|
-
payload,
|
|
283
|
-
secret: certificateString
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
const webResource = this.createWebResource({
|
|
287
|
-
url: audienceUrl,
|
|
288
|
-
method: "POST",
|
|
289
|
-
disableJsonStringifyOnBody: true,
|
|
290
|
-
deserializationMapper: undefined,
|
|
291
|
-
body: qs.stringify({
|
|
292
|
-
response_type: "token",
|
|
293
|
-
grant_type: "client_credentials",
|
|
294
|
-
client_id: clientId,
|
|
295
|
-
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
|
296
|
-
client_assertion: clientAssertion,
|
|
297
|
-
scope: typeof scopes === "string" ? scopes : scopes.join(" ")
|
|
298
|
-
}),
|
|
299
|
-
headers: {
|
|
300
|
-
Accept: "application/json",
|
|
301
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
302
|
-
},
|
|
303
|
-
abortSignal: getTokenOptions && getTokenOptions.abortSignal
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
return this.sendTokenRequest(webResource);
|
|
307
125
|
}
|
|
308
126
|
|
|
309
127
|
static getDefaultOptions(): IdentityClientOptions {
|
|
@@ -314,5 +132,5 @@ export class IdentityClient extends ServiceClient {
|
|
|
314
132
|
}
|
|
315
133
|
|
|
316
134
|
export interface IdentityClientOptions extends ServiceClientOptions {
|
|
317
|
-
authorityHost
|
|
135
|
+
authorityHost?: string;
|
|
318
136
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
5
|
+
|
|
6
|
+
import { TokenCredential, GetTokenOptions, AccessToken } from "@azure/core-http";
|
|
7
|
+
import { IdentityClientOptions } from "../client/identityClient";
|
|
8
|
+
|
|
9
|
+
const BrowserNotSupportedError = new Error("ClientCertificateCredential is not supported in the browser.");
|
|
10
|
+
|
|
11
|
+
export class ClientCertificateCredential implements TokenCredential {
|
|
12
|
+
constructor(
|
|
13
|
+
tenantId: string,
|
|
14
|
+
clientId: string,
|
|
15
|
+
certificatePath: string,
|
|
16
|
+
options?: IdentityClientOptions
|
|
17
|
+
) {
|
|
18
|
+
throw BrowserNotSupportedError;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public getToken(
|
|
22
|
+
scopes: string | string[],
|
|
23
|
+
options?: GetTokenOptions
|
|
24
|
+
): Promise<AccessToken | null> {
|
|
25
|
+
throw BrowserNotSupportedError;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -1,32 +1,45 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
3
|
|
|
4
|
+
import qs from "qs";
|
|
5
|
+
import jws from "jws";
|
|
6
|
+
import uuid from "uuid";
|
|
4
7
|
import { readFileSync } from "fs";
|
|
5
8
|
import { createHash } from "crypto";
|
|
6
9
|
import { TokenCredential, GetTokenOptions, AccessToken } from "@azure/core-http";
|
|
7
10
|
import { IdentityClientOptions, IdentityClient } from "../client/identityClient";
|
|
8
11
|
|
|
12
|
+
const SelfSignedJwtLifetimeMins = 10;
|
|
13
|
+
|
|
14
|
+
function timestampInSeconds(date: Date): number {
|
|
15
|
+
return Math.floor(date.getTime() / 1000);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function addMinutes(date: Date, minutes: number): Date {
|
|
19
|
+
date.setMinutes(date.getMinutes() + minutes);
|
|
20
|
+
return date;
|
|
21
|
+
}
|
|
22
|
+
|
|
9
23
|
/**
|
|
10
24
|
* Enables authentication to Azure Active Directory using a PEM-encoded
|
|
11
25
|
* certificate that is assigned to an App Registration. More information
|
|
12
26
|
* on how to configure certificate authentication can be found here:
|
|
13
|
-
*
|
|
27
|
+
*
|
|
14
28
|
* https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-certificate-credentials#register-your-certificate-with-azure-ad
|
|
15
29
|
*
|
|
16
30
|
*/
|
|
17
31
|
export class ClientCertificateCredential implements TokenCredential {
|
|
18
32
|
private identityClient: IdentityClient;
|
|
19
|
-
private
|
|
20
|
-
private
|
|
21
|
-
private
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
public certificateX5t: string;
|
|
33
|
+
private tenantId: string;
|
|
34
|
+
private clientId: string;
|
|
35
|
+
private certificateString: string;
|
|
36
|
+
private certificateThumbprint: string;
|
|
37
|
+
private certificateX5t: string;
|
|
25
38
|
|
|
26
39
|
/**
|
|
27
40
|
* Creates an instance of the ClientCertificateCredential with the details
|
|
28
41
|
* needed to authenticate against Azure Active Directory with a certificate.
|
|
29
|
-
*
|
|
42
|
+
*
|
|
30
43
|
* @param tenantId The Azure Active Directory tenant (directory) ID.
|
|
31
44
|
* @param clientId The client (application) ID of an App Registration in the tenant.
|
|
32
45
|
* @param certificatePath The path to a PEM-encoded public/private key certificate on the filesystem.
|
|
@@ -39,13 +52,13 @@ export class ClientCertificateCredential implements TokenCredential {
|
|
|
39
52
|
options?: IdentityClientOptions
|
|
40
53
|
) {
|
|
41
54
|
this.identityClient = new IdentityClient(options);
|
|
42
|
-
this.
|
|
43
|
-
this.
|
|
55
|
+
this.tenantId = tenantId;
|
|
56
|
+
this.clientId = clientId;
|
|
44
57
|
|
|
45
|
-
this.
|
|
58
|
+
this.certificateString = readFileSync(certificatePath, "utf8");
|
|
46
59
|
|
|
47
60
|
const certificatePattern = /(-+BEGIN CERTIFICATE-+)(\n\r?|\r\n?)([A-Za-z0-9+/\n\r]+=*)(\n\r?|\r\n?)(-+END CERTIFICATE-+)/;
|
|
48
|
-
const matchCert = this.
|
|
61
|
+
const matchCert = this.certificateString.match(certificatePattern);
|
|
49
62
|
const publicKey = matchCert ? matchCert[3] : "";
|
|
50
63
|
if (!publicKey) {
|
|
51
64
|
throw new Error(
|
|
@@ -66,22 +79,59 @@ export class ClientCertificateCredential implements TokenCredential {
|
|
|
66
79
|
* successful. If authentication cannot be performed at this time, this method may
|
|
67
80
|
* return null. If an error occurs during authentication, an {@link AuthenticationError}
|
|
68
81
|
* containing failure details will be thrown.
|
|
69
|
-
*
|
|
82
|
+
*
|
|
70
83
|
* @param scopes The list of scopes for which the token will have access.
|
|
71
84
|
* @param options The options used to configure any requests this
|
|
72
85
|
* TokenCredential implementation might make.
|
|
73
86
|
*/
|
|
74
|
-
public getToken(
|
|
87
|
+
public async getToken(
|
|
75
88
|
scopes: string | string[],
|
|
76
89
|
options?: GetTokenOptions
|
|
77
90
|
): Promise<AccessToken | null> {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
91
|
+
const tokenId = uuid.v4();
|
|
92
|
+
const audienceUrl = `${this.identityClient.authorityHost}/${this.tenantId}/oauth2/v2.0/token`;
|
|
93
|
+
const header: jws.Header = {
|
|
94
|
+
typ: "JWT",
|
|
95
|
+
alg: "RS256",
|
|
96
|
+
x5t: this.certificateX5t
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const payload = {
|
|
100
|
+
iss: this.clientId,
|
|
101
|
+
sub: this.clientId,
|
|
102
|
+
aud: audienceUrl,
|
|
103
|
+
jti: tokenId,
|
|
104
|
+
nbf: timestampInSeconds(new Date()),
|
|
105
|
+
exp: timestampInSeconds(addMinutes(new Date(), SelfSignedJwtLifetimeMins))
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const clientAssertion = jws.sign({
|
|
109
|
+
header,
|
|
110
|
+
payload,
|
|
111
|
+
secret: this.certificateString
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
const webResource = this.identityClient.createWebResource({
|
|
115
|
+
url: audienceUrl,
|
|
116
|
+
method: "POST",
|
|
117
|
+
disableJsonStringifyOnBody: true,
|
|
118
|
+
deserializationMapper: undefined,
|
|
119
|
+
body: qs.stringify({
|
|
120
|
+
response_type: "token",
|
|
121
|
+
grant_type: "client_credentials",
|
|
122
|
+
client_id: this.clientId,
|
|
123
|
+
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
|
124
|
+
client_assertion: clientAssertion,
|
|
125
|
+
scope: typeof scopes === "string" ? scopes : scopes.join(" ")
|
|
126
|
+
}),
|
|
127
|
+
headers: {
|
|
128
|
+
Accept: "application/json",
|
|
129
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
130
|
+
},
|
|
131
|
+
abortSignal: options && options.abortSignal
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const tokenResponse = await this.identityClient.sendTokenRequest(webResource);
|
|
135
|
+
return (tokenResponse && tokenResponse.accessToken) || null;
|
|
86
136
|
}
|
|
87
137
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Copyright (c) Microsoft Corporation.
|
|
2
2
|
// Licensed under the MIT License.
|
|
3
3
|
|
|
4
|
+
import qs from "qs";
|
|
4
5
|
import { TokenCredential, GetTokenOptions, AccessToken } from "@azure/core-http";
|
|
5
6
|
import { IdentityClientOptions, IdentityClient } from "../client/identityClient";
|
|
6
7
|
|
|
@@ -8,21 +9,21 @@ import { IdentityClientOptions, IdentityClient } from "../client/identityClient"
|
|
|
8
9
|
* Enables authentication to Azure Active Directory using a client secret
|
|
9
10
|
* that was generated for an App Registration. More information on how
|
|
10
11
|
* to configure a client secret can be found here:
|
|
11
|
-
*
|
|
12
|
+
*
|
|
12
13
|
* https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-configure-app-access-web-apis#add-credentials-to-your-web-application
|
|
13
14
|
*
|
|
14
15
|
*/
|
|
15
16
|
export class ClientSecretCredential implements TokenCredential {
|
|
16
17
|
private identityClient: IdentityClient;
|
|
17
|
-
private
|
|
18
|
-
private
|
|
19
|
-
private
|
|
18
|
+
private tenantId: string;
|
|
19
|
+
private clientId: string;
|
|
20
|
+
private clientSecret: string;
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Creates an instance of the ClientSecretCredential with the details
|
|
23
24
|
* needed to authenticate against Azure Active Directory with a client
|
|
24
25
|
* secret.
|
|
25
|
-
*
|
|
26
|
+
*
|
|
26
27
|
* @param tenantId The Azure Active Directory tenant (directory) ID.
|
|
27
28
|
* @param clientId The client (application) ID of an App Registration in the tenant.
|
|
28
29
|
* @param clientSecret A client secret that was generated for the App Registration.
|
|
@@ -35,9 +36,9 @@ export class ClientSecretCredential implements TokenCredential {
|
|
|
35
36
|
options?: IdentityClientOptions
|
|
36
37
|
) {
|
|
37
38
|
this.identityClient = new IdentityClient(options);
|
|
38
|
-
this.
|
|
39
|
-
this.
|
|
40
|
-
this.
|
|
39
|
+
this.tenantId = tenantId;
|
|
40
|
+
this.clientId = clientId;
|
|
41
|
+
this.clientSecret = clientSecret;
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
/**
|
|
@@ -45,21 +46,35 @@ export class ClientSecretCredential implements TokenCredential {
|
|
|
45
46
|
* successful. If authentication cannot be performed at this time, this method may
|
|
46
47
|
* return null. If an error occurs during authentication, an {@link AuthenticationError}
|
|
47
48
|
* containing failure details will be thrown.
|
|
48
|
-
*
|
|
49
|
+
*
|
|
49
50
|
* @param scopes The list of scopes for which the token will have access.
|
|
50
51
|
* @param options The options used to configure any requests this
|
|
51
52
|
* TokenCredential implementation might make.
|
|
52
53
|
*/
|
|
53
|
-
public getToken(
|
|
54
|
+
public async getToken(
|
|
54
55
|
scopes: string | string[],
|
|
55
56
|
options?: GetTokenOptions
|
|
56
57
|
): Promise<AccessToken | null> {
|
|
57
|
-
|
|
58
|
-
this.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
58
|
+
const webResource = this.identityClient.createWebResource({
|
|
59
|
+
url: `${this.identityClient.authorityHost}/${this.tenantId}/oauth2/v2.0/token`,
|
|
60
|
+
method: "POST",
|
|
61
|
+
disableJsonStringifyOnBody: true,
|
|
62
|
+
deserializationMapper: undefined,
|
|
63
|
+
body: qs.stringify({
|
|
64
|
+
response_type: "token",
|
|
65
|
+
grant_type: "client_credentials",
|
|
66
|
+
client_id: this.clientId,
|
|
67
|
+
client_secret: this.clientSecret,
|
|
68
|
+
scope: typeof scopes === "string" ? scopes : scopes.join(" ")
|
|
69
|
+
}),
|
|
70
|
+
headers: {
|
|
71
|
+
Accept: "application/json",
|
|
72
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
73
|
+
},
|
|
74
|
+
abortSignal: options && options.abortSignal
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const tokenResponse = await this.identityClient.sendTokenRequest(webResource);
|
|
78
|
+
return (tokenResponse && tokenResponse.accessToken) || null;
|
|
64
79
|
}
|
|
65
80
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
5
|
+
|
|
6
|
+
import { TokenCredential, GetTokenOptions, AccessToken } from "@azure/core-http";
|
|
7
|
+
import { IdentityClientOptions } from "../client/identityClient";
|
|
8
|
+
|
|
9
|
+
const BrowserNotSupportedError = new Error("DeviceCodeCredential is not supported in the browser.");
|
|
10
|
+
|
|
11
|
+
export class DeviceCodeCredential implements TokenCredential {
|
|
12
|
+
constructor(
|
|
13
|
+
tenantId: string,
|
|
14
|
+
clientId: string,
|
|
15
|
+
userPromptCallback: (details: any) => void,
|
|
16
|
+
options?: IdentityClientOptions
|
|
17
|
+
) {
|
|
18
|
+
throw BrowserNotSupportedError;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public getToken(
|
|
22
|
+
scopes: string | string[],
|
|
23
|
+
options?: GetTokenOptions
|
|
24
|
+
): Promise<AccessToken | null> {
|
|
25
|
+
throw BrowserNotSupportedError;
|
|
26
|
+
}
|
|
27
|
+
}
|