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 372 forks source link

Adal Angular Redirects to login page on handleWindowCallback #647

Closed nicky-ernste closed 6 years ago

nicky-ernste commented 7 years ago

Hello,

I am using Adal Angular (v1.0.15) with TypeScript to login to the AzureAD from my web app. Logging in itself works fine and I get a token back. Then the handleWindowCallback() method is called to actually fill the token and add it to the session as stated by the documentation for that method. Although that method will always do a redirect if popUp is set to false. (Which it is because I do not want to use the pop-up to login). It gets the URL to redirect to from the session using the adal.login.request item. The value of this item always points to the login page because that is where the login request originated from. Setting the session item manually to the desired route fixes the problem but feels like a hack.

My question is why does it always redirect to the page the login originated from? Why can't we do our own redirect in the callback? Or are we required to check if the user is logged in via adal when the login page is loaded and redirect them there when they are logged in? (so the flow would be: login > azureAD > callback > login > dashboard) which also feels like a hassle.

I am just trying to understand how to properly configure adal to get it to work the way I want. I'm hoping you can give some insight into my questions.

My adal configuration:

{
    tenant: 'xxxx.nl',
    clientId: 'xxxxx-44fa-4aec-xxxxx-7628a7f9d380',
    redirectUri: window.location.origin + '/id_token',
    postLogoutRedirectUri: window.location.origin + '/'
}
nicky-ernste commented 7 years ago

I have figured out a way to solve this without setting the session storage myself and without handling it when navigation is returned to the login page. I am now handling this in my callback component as i originally intended because i think it's weird to navigate back to the login again to check if the token was set and then navigate to the desired page.

Instead of using context.handleWindowCallback() i am now doing the following in the callback of the login to set the token and the userInfo of adal.

handleCallback(hash: string) {
    if (!this.context.isCallback(hash))
        return;

    var requestInfo = this.context.getRequestInfo(hash);
    if (requestInfo.valid)
        this.context.saveTokenFromHash(requestInfo); //Save the token and user information.  
}

With this the user can be retrieved with the context.getCachedUser() function in our callback component which then navigates to the desired page. The code for our callback is as follows (for completeness.)

this.adalService.handleCallback(window.location.hash);
if (this.adalService.userInfo)
    this.router.navigate(['dashboard']); //Navigate to user dashboard
else
    this.router.navigate(['']); //Navigate to home page.

While this works nicely as i want it, my question remains if this is the intended way or if there are any disadvantages of handling it like this.

I would appreciate your thoughts on the matter.

rohitnarula7176 commented 7 years ago

@gizmo3399 We get a response to login as a redirect with the token served in the URL fragment and as it is not safe to leave the token in the URL, Adal's default behavior is to remove it by setting the location back to loginStartPage. You can still achieve what you are doing by setting navigateToLoginRequestUrl on the config object to false and then redirecting to the desired page in the callback method.

talorlik commented 6 years ago

@gizmo3399

Thank you! Your handleCallback method works 👍

I'm using ng4-adal module and I've experienced a lot of headache to try and get it too work. There's very little clear documentation and pretty much non existent full, working examples of how to use it.

handleWindowCallback simply doesn't work for me. Maybe I'm doing something but I tried everything I could think of.

Can you please provide an example of how you actually display the report?

Would be so kind to provide a link to a fully fledged adal / powerbi working example in Angular 5? That would be amazing, and my gratitude would know no bounds :-)

My service returns IEmbedConfiguration but for the love of me I'm unable to figure out how to actually take that and use it to display in my template.

I've added angular2-powerbi module but I'm unsure how to make use of it nor is it even needed.

Any help and / or guidance and / or working example would be a life saver.

nicky-ernste commented 6 years ago

@talorlik

Apologies for taking this long to respond.

We use a separate library called: powerbi-ng2 This library is able to actually take the embed url that you receive from the powerBi API and embed that in an element on your page.

I don't really have a stand-alone project I can share with you but I will paste some relevant code snippets that will hopefully help you on your way. (if you haven't already figured it out by now since I took a while to respond).

On the html side it is very simple. All you need to do is create a container element that will hold your embedded dashboard like a div for example:

<div id="powerBiEmbed"></div>

Make sure it has an id because you will need it in the TS code.

On my page I display a list of dashboards that were returned by the PowerBi API and when the user clicks one I load that dashboard in the above div. Below you can see the component I use to render the page.

import { PowerBIService } from 'powerbi-ng2';
import { Component } from "@angular/core";

export class PowerBiComponent {
    dashboards: any[] = []; //List of available dashboards.
    embedToken: string; //The access token retrieved from the PowerBi API.
    currentlyDisplayedDashboard: string;
    embedDiv: HTMLElement; //The div to embed dashboards to.

    constructor(private powerBiService: PowerBIService) { }

    ngAfterViewInit() {
        //Grab the div element to embed dashboards into.
        this.embedDiv = document.getElementById("powerBiEmbed");
    }

    /**
     * Renders a specific dashboard onto the web page.
     * @param dashboardEmbedUrl The embed URL of the dashboard to render on the page.
     */
    private renderDashboard(dashboardEmbedUrl: string): void {
        if (this.currentlyDisplayedDashboard && this.currentlyDisplayedDashboard !== '' || this.currentlyDisplayedDashboard !== dashboardEmbedUrl) {
            //Reset the dashboard if one is currently displayed.
            this.powerBiService.reset(this.embedDiv);
            this.currentlyDisplayedDashboard = '';
        }

        if (!this.embedToken || this.embedToken === "") {
            alert("Cannot embed dashboard");
            return;
        }

        //Configure how to embed the dashboard.
        var embedConfig = {
            type: 'dashboard',
            accessToken: this.embedToken,
            embedUrl: dashboardEmbedUrl
        };
        this.powerBiService.embed(this.embedDiv, embedConfig); //Embed the dashboard into the div element.
        this.currentlyDisplayedDashboard = dashboardEmbedUrl;
    }
}

The PowerBiService from the powerbi-ng2 module will take care of the actual rendering of the dashboard or other supported powerbi type.

I also never use the IEmbedConfiguration directly but I have a feeling it might look similar to the embedConfig variable that I have and give to the embed method of the PowerBiService.

I hope this helps you solve your problem or somebody else's that stumbles onto this.

talorlik commented 6 years ago

Hi Nicky,

Thank you for getting back to me.

I've managed to get the report to show with both powerbi-ng2 and with angular2-powerbi in a very similar way to how you have it in the code you provided.

The one thing that I was unable to do with either one is make use of the: <powerbi-component [embedUrl]="report.embedUrl" [accessToken]="report.accessToken" [type]="report.type" [id]="report.id "> It simply doesn't work.

Also, with powerbi-ng2 the call to find(id) from the service doesn't work. It returns an "undefined".

Regards, Tal Orlik.

On Thu, 25 Jan 2018 at 10:59 Nicky Ernste notifications@github.com wrote:

@talorlik https://github.com/talorlik

Apologies for taking this long to respond.

We use a separate library called: powerbi-ng2 https://www.npmjs.com/package/powerbi-ng2 This library is able to actually take the embed url that you receive from the powerBi API and embed that in an element on your page.

I don't really have a stand-alone project I can share with you but I will paste some relevant code snippets that will hopefully help you on your way. (if you haven't already figured it out by now since I took a while to respond).

On the html side it is very simple. All you need to do is create a container element that will hold your embedded dashboard like a div for example:

Make sure it has an id because you will need it in the TS code.

On my page I display a list of dashboards that were returned by the PowerBi API and when the user clicks one I load that dashboard in the above div. Below you can see the component I use to render the page.

import { PowerBIService } from 'powerbi-ng2';import { Component } from "@angular/core"; export class PowerBiComponent { dashboards: any[] = []; //List of available dashboards. embedToken: string; //The access token retrieved from the PowerBi API. currentlyDisplayedDashboard: string; embedDiv: HTMLElement; //The div to embed dashboards to. constructor(private powerBiService: PowerBIService) { }

ngAfterViewInit() {
    //Grab the div element to embed dashboards into.        this.embedDiv = document.getElementById("powerBiEmbed");
}

/**     * Renders a specific dashboard onto the web page.     * @param dashboardEmbedUrl The embed URL of the dashboard to render on the page.     */
private renderDashboard(dashboardEmbedUrl: string): void {
    if (this.currentlyDisplayedDashboard && this.currentlyDisplayedDashboard !== '' || this.currentlyDisplayedDashboard !== dashboardEmbedUrl) {
        //Reset the dashboard if one is currently displayed.            this.powerBiService.reset(this.embedDiv);
        this.currentlyDisplayedDashboard = '';
    }

    if (!this.embedToken || this.embedToken === "") {
        alert("Cannot embed dashboard");
        return;
    }

    //Configure how to embed the dashboard.        var embedConfig = {
        type: 'dashboard',
        accessToken: this.embedToken,
        embedUrl: dashboardEmbedUrl
    };
    this.powerBiService.embed(this.embedDiv, embedConfig); //Embed the dashboard into the div element.        this.currentlyDisplayedDashboard = dashboardEmbedUrl;
}

}

The PowerBiService from the powerbi-ng2 module will take care of the actual rendering of the dashboard or other supported powerbi type.

I also never use the IEmbedConfiguration directly but I have a feeling it might look similar to the embedConfig variable that I have and give to the embed method of the PowerBiService.

I hope this helps you solve your problem or somebody else's that stumbles onto this.

— 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/647#issuecomment-360402562, or mute the thread https://github.com/notifications/unsubscribe-auth/AJjsf3v6R2gKnfIvi1MF_cgkOrd2F0Jwks5tOEJZgaJpZM4QM6QF .

djcurtis2 commented 6 years ago

@talorlik I was able to use <powerbi-component [embedUrl]="report.embedUrl" [accessToken]="report.accessToken" [type]="report.type" [id]="report.id ">.

The trick for me was to use the token I received from "https://login.microsoftonline.com/common/oauth2/authorize?resource=https://analysis.windows.net/powerbi/api", and NOT the Power BI embed token.

HofmT commented 6 years ago

@rohitnarula7176 I face a similar issue. After logging into Azure AD, I don't want to be redirected to the loginpage instead i want to be redirected to the root index. I set the configuration navigateToLoginRequestUrl to false. Now, after logging in it does show the root index path in the address bar of my browser however the page is completely blank. I assume that I still have to modify the callback to do the redirect however I am still missing information on how to do it. Can you give me a hint?

dgodwin1175 commented 6 years ago

@HofmT, I had the same problem, after successful login, or logout, I wanted to redirect the user back to the redirectUri only, and not to the loginStartPage (which could would generally be an angular state such as "redirectUri/state/substate"). To support this, I made some changes to the library to populate loginStartPage from adal.config.redirectUri.