@@ -11,9 +11,10 @@ import { Socket } from "net";
1111import { AuthenticationRequired , MsalClient } from "../client/msalClient" ;
1212import { AuthorizationCodeRequest } from "@azure/msal-node" ;
1313
14- import express from "express" ;
1514import open from "open" ;
1615import http from "http" ;
16+ import stoppable from "stoppable" ;
17+
1718import { checkTenantId } from "../util/checkTenantId" ;
1819
1920const logger = credentialLogger ( "InteractiveBrowserCredential" ) ;
@@ -26,6 +27,7 @@ const logger = credentialLogger("InteractiveBrowserCredential");
2627export class InteractiveBrowserCredential implements TokenCredential {
2728 private redirectUri : string ;
2829 private port : number ;
30+ private hostname : string ;
2931 private msalClient : MsalClient ;
3032
3133 constructor ( options : InteractiveBrowserCredentialOptions = { } ) {
@@ -53,6 +55,8 @@ export class InteractiveBrowserCredential implements TokenCredential {
5355 this . port = 80 ;
5456 }
5557
58+ this . hostname = url . hostname ;
59+
5660 let authorityHost ;
5761 if ( options . authorityHost ) {
5862 if ( options . authorityHost . endsWith ( "/" ) ) {
@@ -112,74 +116,111 @@ export class InteractiveBrowserCredential implements TokenCredential {
112116 await open ( response ) ;
113117 }
114118
115- private async acquireTokenFromBrowser ( scopeArray : string [ ] ) : Promise < AccessToken | null > {
116- // eslint-disable-next-line
117- return new Promise < AccessToken | null > ( async ( resolve , reject ) => {
118- // eslint-disable-next-line
119- let listen : http . Server | undefined ;
120- let socketToDestroy : Socket | undefined ;
119+ private acquireTokenFromBrowser ( scopeArray : string [ ] ) : Promise < AccessToken | null > {
120+ return new Promise < AccessToken | null > ( ( resolve , reject ) => {
121+ const socketToDestroy : Socket [ ] = [ ] ;
121122
122- function cleanup ( ) : void {
123- if ( listen ) {
124- listen . close ( ) ;
123+ const requestListener = ( req : http . IncomingMessage , res : http . ServerResponse ) => {
124+ if ( ! req . url ) {
125+ reject (
126+ new Error (
127+ `Interactive Browser Authentication Error "Did not receive token with a valid expiration"`
128+ )
129+ ) ;
130+ return ;
125131 }
126- if ( socketToDestroy ) {
127- socketToDestroy . destroy ( ) ;
132+ let url : URL ;
133+ try {
134+ url = new URL ( req . url , this . redirectUri ) ;
135+ } catch ( e ) {
136+ reject (
137+ new Error (
138+ `Interactive Browser Authentication Error "Did not receive token with a valid expiration"`
139+ )
140+ ) ;
141+ return ;
128142 }
129- }
130-
131- // Create Express App and Routes
132- const app = express ( ) ;
133-
134- app . get ( "/" , async ( req , res ) => {
135143 const tokenRequest : AuthorizationCodeRequest = {
136- code : req . query . code as string ,
144+ code : url . searchParams . get ( " code" ) ! ,
137145 redirectUri : this . redirectUri ,
138146 scopes : scopeArray
139147 } ;
140148
141- try {
142- const authResponse = await this . msalClient . acquireTokenByCode ( tokenRequest ) ;
143- const successMessage = `Authentication Complete. You can close the browser and return to the application.` ;
144- if ( authResponse && authResponse . expiresOn ) {
145- const expiresOnTimestamp = authResponse ?. expiresOn . valueOf ( ) ;
146- res . status ( 200 ) . send ( successMessage ) ;
147- logger . getToken . info ( formatSuccess ( scopeArray ) ) ;
148-
149- resolve ( {
150- expiresOnTimestamp,
151- token : authResponse . accessToken
152- } ) ;
153- } else {
149+ this . msalClient
150+ . acquireTokenByCode ( tokenRequest )
151+ . then ( ( authResponse ) => {
152+ const successMessage = `Authentication Complete. You can close the browser and return to the application.` ;
153+ if ( authResponse && authResponse . expiresOn ) {
154+ const expiresOnTimestamp = authResponse ?. expiresOn . valueOf ( ) ;
155+ res . writeHead ( 200 ) ;
156+ res . end ( successMessage ) ;
157+ logger . getToken . info ( formatSuccess ( scopeArray ) ) ;
158+
159+ resolve ( {
160+ expiresOnTimestamp,
161+ token : authResponse . accessToken
162+ } ) ;
163+ } else {
164+ const errorMessage = formatError (
165+ scopeArray ,
166+ `${ url . searchParams . get ( "error" ) } . ${ url . searchParams . get ( "error_description" ) } `
167+ ) ;
168+ res . writeHead ( 500 ) ;
169+ res . end ( errorMessage ) ;
170+ logger . getToken . info ( errorMessage ) ;
171+
172+ reject (
173+ new Error (
174+ `Interactive Browser Authentication Error "Did not receive token with a valid expiration"`
175+ )
176+ ) ;
177+ }
178+ cleanup ( ) ;
179+ return ;
180+ } )
181+ . catch ( ( ) => {
182+ const errorMessage = formatError (
183+ scopeArray ,
184+ `${ url . searchParams . get ( "error" ) } . ${ url . searchParams . get ( "error_description" ) } `
185+ ) ;
186+ res . writeHead ( 500 ) ;
187+ res . end ( errorMessage ) ;
188+ logger . getToken . info ( errorMessage ) ;
189+
154190 reject (
155191 new Error (
156192 `Interactive Browser Authentication Error "Did not receive token with a valid expiration"`
157193 )
158194 ) ;
159- }
160- } catch ( error ) {
161- const errorMessage = formatError (
162- scopeArray ,
163- `${ req . query [ "error" ] } . ${ req . query [ "error_description" ] } `
164- ) ;
165- res . status ( 500 ) . send ( errorMessage ) ;
166- logger . getToken . info ( errorMessage ) ;
167- reject ( new Error ( errorMessage ) ) ;
168- } finally {
169- cleanup ( ) ;
170- }
171- } ) ;
195+ cleanup ( ) ;
196+ } ) ;
197+ } ;
198+ const app = http . createServer ( requestListener ) ;
172199
173- listen = app . listen ( this . port , ( ) =>
200+ const listen = app . listen ( this . port , this . hostname , ( ) =>
174201 logger . info ( `InteractiveBrowerCredential listening on port ${ this . port } !` )
175202 ) ;
176- listen . on ( "connection" , ( socket ) => ( socketToDestroy = socket ) ) ;
203+ app . on ( "connection" , ( socket ) => socketToDestroy . push ( socket ) ) ;
204+ const server = stoppable ( app ) ;
177205
178- try {
179- await this . openAuthCodeUrl ( scopeArray ) ;
180- } catch ( e ) {
206+ this . openAuthCodeUrl ( scopeArray ) . catch ( ( e ) => {
181207 cleanup ( ) ;
182- throw e ;
208+ reject ( e ) ;
209+ } ) ;
210+
211+ function cleanup ( ) : void {
212+ if ( listen ) {
213+ listen . close ( ) ;
214+ }
215+
216+ for ( const socket of socketToDestroy ) {
217+ socket . destroy ( ) ;
218+ }
219+
220+ if ( server ) {
221+ server . close ( ) ;
222+ server . stop ( ) ;
223+ }
183224 }
184225 } ) ;
185226 }
0 commit comments