Skip to content

Commit e485dc2

Browse files
improve logic around auth server
1 parent a0dd9ed commit e485dc2

File tree

3 files changed

+186
-175
lines changed

3 files changed

+186
-175
lines changed

extensions/microsoft-authentication/media/auth.html renamed to extensions/microsoft-authentication/media/index.html

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->
22
<!DOCTYPE html>
3-
<html>
3+
<html lang="en">
44

55
<head>
66
<meta charset="utf-8" />
7-
<meta http-equiv="X-UA-Compatible" content="IE=edge">
87
<title>Azure Account - Sign In</title>
98
<meta name="viewport" content="width=device-width, initial-scale=1">
109
<link rel="stylesheet" type="text/css" media="screen" href="auth.css" />

extensions/microsoft-authentication/src/AADHelper.ts

Lines changed: 32 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ import * as vscode from 'vscode';
1010
import * as nls from 'vscode-nls';
1111
import { v4 as uuid } from 'uuid';
1212
import fetch, { Response } from 'node-fetch';
13-
import { createServer, startServer } from './authServer';
1413
import { Keychain } from './keychain';
1514
import Logger from './logger';
1615
import { toBase64UrlEncoding } from './utils';
1716
import { sha256 } from './env/node/sha256';
1817
import { BetterTokenStorage, IDidChangeInOtherWindowEvent } from './betterSecretStorage';
18+
import { LoopbackAuthServer } from './authServer';
19+
import path = require('path');
1920

2021
const localize = nls.loadMessageBundle();
2122

@@ -238,63 +239,42 @@ export class AzureActiveDirectoryService {
238239
}
239240

240241
private async createSessionWithLocalServer(scopeData: IScopeData) {
241-
const nonce = randomBytes(16).toString('base64');
242-
const { server, redirectPromise, codePromise } = createServer(nonce);
242+
const codeVerifier = toBase64UrlEncoding(randomBytes(32).toString('base64'));
243+
const codeChallenge = toBase64UrlEncoding(await sha256(codeVerifier));
244+
const qs = querystring.stringify({
245+
response_type: 'code',
246+
response_mode: 'query',
247+
client_id: scopeData.clientId,
248+
redirect_uri: redirectUrl,
249+
scope: scopeData.scopesToSend,
250+
prompt: 'select_account',
251+
code_challenge_method: 'S256',
252+
code_challenge: codeChallenge,
253+
});
254+
const loginUrl = `${loginEndpointUrl}${scopeData.tenant}/oauth2/v2.0/authorize?${qs}`;
255+
const server = new LoopbackAuthServer(path.join(__dirname, '../media'), loginUrl);
256+
await server.start();
257+
server.state = `${server.port},${encodeURIComponent(server.nonce)}`;
243258

244-
let token: IToken | undefined;
259+
let codeToExchange;
245260
try {
246-
const port = await startServer(server);
247-
vscode.env.openExternal(vscode.Uri.parse(`http://localhost:${port}/signin?nonce=${encodeURIComponent(nonce)}`));
248-
249-
const redirectReq = await redirectPromise;
250-
if ('err' in redirectReq) {
251-
const { err, res } = redirectReq;
252-
res.writeHead(302, { Location: `/?error=${encodeURIComponent(err && err.message || 'Unknown error')}` });
253-
res.end();
254-
throw err;
255-
}
256-
257-
const host = redirectReq.req.headers.host || '';
258-
const updatedPortStr = (/^[^:]+:(\d+)$/.exec(Array.isArray(host) ? host[0] : host) || [])[1];
259-
const updatedPort = updatedPortStr ? parseInt(updatedPortStr, 10) : port;
260-
261-
const state = `${updatedPort},${encodeURIComponent(nonce)}`;
262-
263-
const codeVerifier = toBase64UrlEncoding(randomBytes(32).toString('base64'));
264-
const codeChallenge = toBase64UrlEncoding(await sha256(codeVerifier));
265-
266-
const loginUrl = `${loginEndpointUrl}${scopeData.tenant}/oauth2/v2.0/authorize?response_type=code&response_mode=query&client_id=${encodeURIComponent(scopeData.clientId)}&redirect_uri=${encodeURIComponent(redirectUrl)}&state=${state}&scope=${encodeURIComponent(scopeData.scopesToSend)}&prompt=select_account&code_challenge_method=S256&code_challenge=${codeChallenge}`;
267-
268-
redirectReq.res.writeHead(302, { Location: loginUrl });
269-
redirectReq.res.end();
270-
271-
const codeRes = await codePromise;
272-
const res = codeRes.res;
273-
274-
try {
275-
if ('err' in codeRes) {
276-
throw codeRes.err;
277-
}
278-
token = await this.exchangeCodeForToken(codeRes.code, codeVerifier, scopeData);
279-
if (token.expiresIn) {
280-
this.setSessionTimeout(token.sessionId, token.refreshToken, scopeData, token.expiresIn * AzureActiveDirectoryService.REFRESH_TIMEOUT_MODIFIER);
281-
}
282-
await this.setToken(token, scopeData);
283-
Logger.info(`Login successful for scopes: ${scopeData.scopeStr}`);
284-
res.writeHead(302, { Location: '/' });
285-
const session = await this.convertToSession(token);
286-
return session;
287-
} catch (err) {
288-
res.writeHead(302, { Location: `/?error=${encodeURIComponent(err && err.message || 'Unknown error')}` });
289-
throw err;
290-
} finally {
291-
res.end();
292-
}
261+
vscode.env.openExternal(vscode.Uri.parse(`http://localhost:${server.port}/signin?nonce=${encodeURIComponent(server.nonce)}`));
262+
const { code } = await server.waitForOAuthResponse();
263+
codeToExchange = code;
293264
} finally {
294265
setTimeout(() => {
295-
server.close();
266+
void server.stop();
296267
}, 5000);
297268
}
269+
270+
const token = await this.exchangeCodeForToken(codeToExchange, codeVerifier, scopeData);
271+
if (token.expiresIn) {
272+
this.setSessionTimeout(token.sessionId, token.refreshToken, scopeData, token.expiresIn * AzureActiveDirectoryService.REFRESH_TIMEOUT_MODIFIER);
273+
}
274+
await this.setToken(token, scopeData);
275+
Logger.info(`Login successful for scopes: ${scopeData.scopeStr}`);
276+
const session = await this.convertToSession(token);
277+
return session;
298278
}
299279

300280
private async createSessionWithoutLocalServer(scopeData: IScopeData): Promise<vscode.AuthenticationSession> {

0 commit comments

Comments
 (0)