AzureAD / azure-activedirectory-library-for-js

The code for ADAL.js and ADAL Angular has been moved to the MSAL.js repo. Please open any issues or PRs at the link below.
https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/maintenance/adal-angular
Apache License 2.0
627 stars 374 forks source link

AquireToken with multiple endpoints api calls is loading the entire application #649

Closed riteshmeher closed 6 years ago

riteshmeher commented 6 years ago

We are using adal-angular4 wrapper for adal.js in angular 4 cli . when one of our service calls any backend api , it is refreshing the entire application after calling the AcquireToken method. It starts loading script.bundle.js, styles.bundle.js etc. again. I can see IFrames getting created for every endpoint calls. We have a scenario where we are calling multiple endpoints sequentially to fetch data. we get response for the first endpoint only. Can anyone help me on this. adal config:

    tenant: '<tenant>',
    instance: '<instance>',
    clientId: '<clientId>',
    endpoints: <endpointsarray>,
    redirectUri: window.location.origin

@rohitnarula7176 Please let me know if there are any workaround

rohitnarula7176 commented 6 years ago

@riteshmeher Can you please provide a code repro for this issue?

riteshmeher commented 6 years ago

@rohitnarula7176

export const base=window.location.origin; // this is configured in adfs
export const environment: IAppConfig={
    isProduction: false,
    adalConfig: {
        tenant: '<tenant>',
        instance: '<instance>',
        clientId: '<clientId>',
        endpoints: <endpointsarray>,
        redirectUri: base
    }

    app.route.ts
    {
        path: 'home',
        component: HomeComponent
    },
    {
        path: '',
        component: LoginComponent
    }

    logincomponent.ts
    ngOnInit(): void{
        if(window.location.hash!==''){
            this.service.handleWindowCallback();
        }//Checkiftheuserisauthenticated.Ifnot,
        callthelogin()methodif(!this.service.userInfo.authenticated){
            this.service.login();
        }else{
            this.router.navigate(['home']);
        }

    ======================
    get<T>(url: string, data?: any, options?: RequestOptionsArgs): Observable<IResponse<T>> {
    let options1 = this.getRequestOption(RequestMethod.Get, data);
    options1 = options1.merge(options);
    return this.sendRequest<T>(url, options1);
    }

    //sendRequest: this is called from a http request
    private sendRequest<T>(url: string, options: RequestOptionsArgs): Observable<IResponse<T>> {
    // make a copy
    let options1 = new RequestOptions();
    options1.method = options.method;
    options1 = options1.merge(options);

    const resource = this.service.GetResourceForEndpoint(url);
    let authenticatedCall: Observable<IResponse<T>>;
    if (resource) {
      if (this.service.userInfo.authenticated) {
        authenticatedCall = this.service.acquireToken(resource)
          .flatMap((token: string) => {
            // tslint:disable-next-line:no-console
            console.log(url, token);
            if (options1.headers == null) {
              options1.headers = new Headers();
            }
            if (!options1.headers.has('Content-Type')) {
              options1.headers.append('Content-Type', 'application/json');
            }
            options1.headers.append('Authorization', 'Bearer ' + token);
            return this.http.request(url, options1).map(response => {
              var apiRespose: IAPIResponse<T> = response.json();
              var result = this.handleAPIResponse<T>(apiRespose, url);
              return result;
            },
            ).catch((error: any) => {
              return this.handleError<T>(error.status, url);
            });
          });
      }
      else {
        // tslint:disable-next-line:no-console
        console.log(this.service.userInfo);
        authenticatedCall = Observable.throw(new Error('User Not Authenticated.'));
      }
    }
    else {
      authenticatedCall = this.http.request(url, options).map(response => {
        var apiRespose: IAPIResponse<T> = response.json();
        var result = this.handleAPIResponse<T>(apiRespose, url);
        return result;
      },
      ).catch((error: any) => {
        return this.handleError<T>(error.status, url);
      });
    }

    return authenticatedCall;
  }

I noticed http calls are getting cancelled. Below are the headers. (chrome) Provisional headers are shown Access-Control-Request-Headers:authorization,content-type Access-Control-Request-Method:GET Origin:http://localhost:4200 Referer:http://localhost:4200/home

looks like because of this it keeps reloading entire application again. If we refresh the application it starts working again without any issue. This is only for the first time redirecting to home.

riteshmeher commented 6 years ago

@rohitnarula7176 can you help me on this. Its blocking everything in our application.

rohitnarula7176 commented 6 years ago

@riteshmeher You are using the adal-angular4 package which is not written by Microsoft. In order for me to help you on this, I would need a working code and instructions on how to run it. If you can create a private repo on github and send me a link to it, I will be able to look into it.

riteshmeher commented 6 years ago

Here is the link where I have pushed a demo code. https://github.com/riteshmeher/adal-angular4

jmccannster commented 6 years ago

@riteshmeher have you had any luck with this? I'm experiencing the same. I'm able to login to Azure, but subsequent calls to my back-end service results in refreshes.

riteshmeher commented 6 years ago

@jmccannster unfortunately no. I have not got any solution or any workaround yet. This looks crazy to load an entire app every time we request a new token for a resource. This makes our app dead slow.

jmccannster commented 6 years ago

Agree. For now I have my client and API athenticating to the same AD app using ADAL on the client. I'm then bypassing ADAL and calling API with the standard HttpClient and sticking the token in the header myself. I would like to get this working properly, but out of time.

On Dec 23, 2017 8:56 AM, "Ritesh Meher" notifications@github.com wrote:

@jmccannster https://github.com/jmccannster unfortunately no. I have not got any solution or any workaround yet. This looks crazy to load an entire app every time we request a new token for a resource. This makes our app dead slow.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/AzureAD/azure-activedirectory-library-for-js/issues/649#issuecomment-353736697, or mute the thread https://github.com/notifications/unsubscribe-auth/AET6MG9L163IAQrzwnr4arhlNhaUv76sks5tDTCogaJpZM4QODmb .

riteshmeher commented 6 years ago

what I see in adal library is when acquireToken method is called, it is reloading the app to read the token from the url in handlewindow.. method. I am not sure how we can we get the resource token directly calling any adfs endpoint. If we can get the token directly then we can handle the refreshing token by ourselves. This has a trade off to renew token by our code but I believe its worth it. This will stop the loading app every time we ask for a new token.

rohitnarula7176 commented 6 years ago

@riteshmeher Adaljs uses implicit flow which means that all the tokens are received as part of a redirect response with the token served in the url fragment. There is no concept of refresh token in the implicit flow. A redirect response (302) causes an app reload by definition and the only way to prevent it in adal is by setting the redirectUri to something other than your app's root page. You can refer to adal's wiki on how to achieve this. Please let me know if you have further questions.