Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Login redirect a user with Azure AD B2C from an Angular application and msal.js

I'm trying to get an Angular 4 app to correctly do an implicit authentication with Azure AD B2C. I'm using msal.js to try and make it to work. I've checked the very limited official sample code, but it's of no real use as it's using a popup to login and I want to do a redirection.

What I have now is the following authentication service that I'm injecting in my app and that should take care of all the authentication.

import { Injectable } from '@angular/core';
import * as Msal from 'msal';

@Injectable()
export class AuthenticationService {

    private tenantConfig = {
        tenant: "example.onmicrosoft.com",
        clientID: "redacted (guid of the client)",
        signUpSignInPolicy: "b2c_1_signup",
        b2cScopes: ["https://example.onmicrosoft.com/demo/read", "openid"]
    }

    private authority = "https://login.microsoftonline.com/tfp/" + this.tenantConfig.tenant + "/" + this.tenantConfig.signUpSignInPolicy;

    private clientApplication: Msal.UserAgentApplication;

    constructor() {
        this.clientApplication = new Msal.UserAgentApplication(this.tenantConfig.clientID, this.authority, this.authCallback);
    }

    public login(): void {
        this.clientApplication.loginRedirect(this.tenantConfig.b2cScopes);
    }

    public logout(): void {
        this.clientApplication.logout();
    }

    public isOnline(): boolean {
        return this.clientApplication.getUser() != null;
    }

    public getUser(): Msal.User {
        return this.clientApplication.getUser();
    }

    public getAuthenticationToken(): Promise<string> {
        return this.clientApplication.acquireTokenSilent(this.tenantConfig.b2cScopes)
            .then(token => {
                console.log("Got silent access token: ", token);
                return token;
            }).catch(error => {
                console.log("Could not silently retrieve token from storage.", error);
                return this.clientApplication.acquireTokenPopup(this.tenantConfig.b2cScopes)
                    .then(token => {
                        console.log("Got popup access token: ", token);
                        return token;
                    }).catch(error => {
                        console.log("Could not retrieve token from popup.", error);
                        this.clientApplication.acquireTokenRedirect(this.tenantConfig.b2cScopes);
                        return Promise.resolve("");
                    });
            });
    }

    private authCallback(errorDesc: any, token: any, error: any, tokenType: any) {
        console.log("Callback")

        if (token) {
            console.log("Id token", token);
        }
        else {
            console.log(error + ":" + errorDesc);
        }

        this.getAuthenticationToken();
    }
}

But it's not working. I get the ID token correctly, and it's valid, so the "Id token" does receive a value that I can use, but for a limited time.

The issue is when I try to get the authentication token. The first call, to acquireTokenSilent returns an error that says : Token renewal operation failed due to timeout: null.

Then, I'm getting a popup that asks for a username and password, which, after a while disappears and I get an error that says User does not have an existing session and request prompt parameter has a value of 'None'..

Edit:

So, I think I understand what's going on exactly and to reproduce the issue on a sample application that you can get here: https://github.com/Gimly/NetCoreAngularAzureB2CMsal

If you connect from the homepage and then go to the fetchData page (the one with the weather forecast) you can see the auth token be correctly redeemed by acquireTokenSilent (open the browser console to get all the logs). But, if you refresh the page directly on fetchData, you can see the same behavior that I described, with acquireTokenSilent failing with the error about a timeout.

My best guess would be that, fore some reason, even though getUser returns a correct value, msal is not completely initialized before I call getAuthenticationToken and it's the reason for it to fail completely.

Now the real question... How do I make sure that it's completely initialized before trying to get the token?

like image 933
Gimly Avatar asked Oct 17 '17 14:10

Gimly


1 Answers

OK, so the issue I was facing was caused by two things:

1) a bug in msal.js that caused the initialization process to not be fully completed at the end of the constructor call, and I was calling the acquireTokenSilent() before it had fully initialized. This was fixed in the latest version (as of writing, still in the dev branch of the project). See this GitHub issue for more info.

2) I hadn't fixed the redirect URI, which caused the library to take the current URL as redirect URI which would fail because it wasn't in the list of allowed redirect URIs.

After those two issues fixed, the code I posted in my question is working as expected.

like image 70
Gimly Avatar answered Oct 15 '22 01:10

Gimly