Skip to content
This repository has been archived by the owner on Apr 8, 2023. It is now read-only.

Commit

Permalink
[Identity] Remove express (Azure#13800)
Browse files Browse the repository at this point in the history
* Remove use of express in InteractiveBrowserCredential

* Remove use of express in InteractiveBrowserCredential

* Fix lint issues

* Use hostname instead of host

* Add changelog entry

* Address feedback

* Address feedback

* lint

* address feedback

* address feedback

* lint
  • Loading branch information
Jonathan Turner authored Mar 25, 2021
1 parent 527a7b3 commit 543d56a
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 54 deletions.
4 changes: 2 additions & 2 deletions sdk/identity/identity/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,14 @@
"@azure/logger": "^1.0.0",
"@azure/msal-node": "1.0.0-beta.6",
"@opentelemetry/api": "^0.10.2",
"@types/stoppable": "^1.1.0",
"axios": "^0.21.1",
"events": "^3.0.0",
"jws": "^4.0.0",
"msal": "^1.0.2",
"open": "^7.0.0",
"qs": "^6.7.0",
"stoppable": "^1.1.0",
"tslib": "^2.0.0",
"uuid": "^8.3.0"
},
Expand All @@ -107,7 +109,6 @@
"@rollup/plugin-multi-entry": "^3.0.0",
"@rollup/plugin-node-resolve": "^8.0.0",
"@rollup/plugin-replace": "^2.2.0",
"@types/express": "^4.16.0",
"@types/jws": "^3.2.2",
"@types/mocha": "^7.0.2",
"@types/node": "^8.0.0",
Expand All @@ -116,7 +117,6 @@
"assert": "^1.4.1",
"cross-env": "^7.0.2",
"eslint": "^7.15.0",
"express": "^4.16.3",
"inherits": "^2.0.3",
"karma": "^5.1.0",
"karma-chrome-launcher": "^3.0.0",
Expand Down
145 changes: 93 additions & 52 deletions sdk/identity/identity/src/credentials/interactiveBrowserCredential.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import { Socket } from "net";
import { AuthenticationRequired, MsalClient } from "../client/msalClient";
import { AuthorizationCodeRequest } from "@azure/msal-node";

import express from "express";
import open from "open";
import http from "http";
import stoppable from "stoppable";

import { checkTenantId } from "../util/checkTenantId";

const logger = credentialLogger("InteractiveBrowserCredential");
Expand All @@ -26,6 +27,7 @@ const logger = credentialLogger("InteractiveBrowserCredential");
export class InteractiveBrowserCredential implements TokenCredential {
private redirectUri: string;
private port: number;
private hostname: string;
private msalClient: MsalClient;

constructor(options?: InteractiveBrowserCredentialOptions) {
Expand Down Expand Up @@ -53,6 +55,8 @@ export class InteractiveBrowserCredential implements TokenCredential {
this.port = 80;
}

this.hostname = url.hostname;

let authorityHost;
if (options && options.authorityHost) {
if (options.authorityHost.endsWith("/")) {
Expand Down Expand Up @@ -112,74 +116,111 @@ export class InteractiveBrowserCredential implements TokenCredential {
await open(response);
}

private async acquireTokenFromBrowser(scopeArray: string[]): Promise<AccessToken | null> {
// eslint-disable-next-line
return new Promise<AccessToken | null>(async (resolve, reject) => {
// eslint-disable-next-line
let listen: http.Server | undefined;
let socketToDestroy: Socket | undefined;
private acquireTokenFromBrowser(scopeArray: string[]): Promise<AccessToken | null> {
return new Promise<AccessToken | null>((resolve, reject) => {
const socketToDestroy: Socket[] = [];

function cleanup(): void {
if (listen) {
listen.close();
const requestListener = (req: http.IncomingMessage, res: http.ServerResponse) => {
if (!req.url) {
reject(
new Error(
`Interactive Browser Authentication Error "Did not receive token with a valid expiration"`
)
);
return;
}
if (socketToDestroy) {
socketToDestroy.destroy();
let url: URL;
try {
url = new URL(req.url, this.redirectUri);
} catch (e) {
reject(
new Error(
`Interactive Browser Authentication Error "Did not receive token with a valid expiration"`
)
);
return;
}
}

// Create Express App and Routes
const app = express();

app.get("/", async (req, res) => {
const tokenRequest: AuthorizationCodeRequest = {
code: req.query.code as string,
code: url.searchParams.get("code")!,
redirectUri: this.redirectUri,
scopes: scopeArray
};

try {
const authResponse = await this.msalClient.acquireTokenByCode(tokenRequest);
const successMessage = `Authentication Complete. You can close the browser and return to the application.`;
if (authResponse && authResponse.expiresOn) {
const expiresOnTimestamp = authResponse?.expiresOn.valueOf();
res.status(200).send(successMessage);
logger.getToken.info(formatSuccess(scopeArray));

resolve({
expiresOnTimestamp,
token: authResponse.accessToken
});
} else {
this.msalClient
.acquireTokenByCode(tokenRequest)
.then((authResponse) => {
const successMessage = `Authentication Complete. You can close the browser and return to the application.`;
if (authResponse && authResponse.expiresOn) {
const expiresOnTimestamp = authResponse?.expiresOn.valueOf();
res.writeHead(200);
res.end(successMessage);
logger.getToken.info(formatSuccess(scopeArray));

resolve({
expiresOnTimestamp,
token: authResponse.accessToken
});
} else {
const errorMessage = formatError(
scopeArray,
`${url.searchParams.get("error")}. ${url.searchParams.get("error_description")}`
);
res.writeHead(500);
res.end(errorMessage);
logger.getToken.info(errorMessage);

reject(
new Error(
`Interactive Browser Authentication Error "Did not receive token with a valid expiration"`
)
);
}
cleanup();
return;
})
.catch(() => {
const errorMessage = formatError(
scopeArray,
`${url.searchParams.get("error")}. ${url.searchParams.get("error_description")}`
);
res.writeHead(500);
res.end(errorMessage);
logger.getToken.info(errorMessage);

reject(
new Error(
`Interactive Browser Authentication Error "Did not receive token with a valid expiration"`
)
);
}
} catch (error) {
const errorMessage = formatError(
scopeArray,
`${req.query["error"]}. ${req.query["error_description"]}`
);
res.status(500).send(errorMessage);
logger.getToken.info(errorMessage);
reject(new Error(errorMessage));
} finally {
cleanup();
}
});
cleanup();
});
};
const app = http.createServer(requestListener);

listen = app.listen(this.port, () =>
logger.info(`Msal Node Auth Code Sample app listening on port ${this.port}!`)
const listen = app.listen(this.port, this.hostname, () =>
logger.info(`InteractiveBrowerCredential listening on port ${this.port}!`)
);
listen.on("connection", (socket) => (socketToDestroy = socket));
app.on("connection", (socket) => socketToDestroy.push(socket));
const server = stoppable(app);

try {
await this.openAuthCodeUrl(scopeArray);
} catch (e) {
this.openAuthCodeUrl(scopeArray).catch((e) => {
cleanup();
throw e;
reject(e);
});

function cleanup(): void {
if (listen) {
listen.close();
}

for (const socket of socketToDestroy) {
socket.destroy();
}

if (server) {
server.close();
server.stop();
}
}
});
}
Expand Down

0 comments on commit 543d56a

Please sign in to comment.