2
votes

Auth0 requires you to whitelist callback URLs after authentication so you can't just login on any page in your application with URLs like /thing/1, /thing/1001 as there's no way to wildcard the thing IDs.

This Github conversation points to neat solution which I have interpreted in Angular 2 as:

In my auth.service.ts:

lock = new Auth0Lock('Client_ID', 'Domain', {
        auth: {
            redirectUrl: window.location.origin + '/login',
            responseType: 'token',
            params: {
                scope: 'openid name email',
                state: JSON.stringify({pathname: window.location.pathname})
            }
        }
    });

I can then whitelist my /login route in Angular 2. I tried getting my login.component.ts to read and then navigate to the pathname returned by Auth0 in the call back like this:

this.route
    .queryParams
    .subscribe(params => {
         this.state = params['state'];
         this.router.navigate(this.state, {preserveQueryParams: true});
     });

...but params always seems to be empty. As far as I can tell this is because the call back URL is of this form:

http://localhost:5555/login#access_token=...&id_token=...&token_type=Bearer&state=%7B%22pathname%22%3A%22%2Flogin%22%7D

...and Angular 2 routing automatically strips off the # parameters before they get to the LoginComponent.

I then spotted that authResult on lock.authenticated still contains my state parameter in auth.service.ts so attempted to navigate to it as follows:

this.lock.on("authenticated", (authResult:any) => {
    localStorage.setItem('id_token', authResult.idToken);
    let state: string = JSON.parse(authResult.state);
    this.router.navigate([state.pathname], {});

This seemed to work initially but, it turns out, not reliably...it seems to redirect me unpredictably anywhere from

/thing/1001 to 
/things or even 
/

...and I can't work out why. Any help very much appreciated.

EDIT In response to @shusson's answer:

Working code based on @shusson's answer is in auth.service.ts:

export class Auth {
    // Configure Auth0
    lock = new Auth0Lock('Client_ID', 'Domain',{
        auth: {
            redirectUrl: location.origin + '/login',
            responseType: 'token',
        }
    });

    constructor(private router: Router, route: ActivatedRoute) {

        // Add callback for lock `authenticated` event
        this.lock.on("authenticated", (authResult:any) => {
            localStorage.setItem('id_token', authResult.idToken);
            let state: any = JSON.parse(authResult.state);
            this.router.navigate([state.pathname], {});
        ...
     }

    public login() {
        // Call the show method to display the widget.
        this.lock.show({
            auth: {
                params: {
                    scope: 'openid name email',
                    state: JSON.stringify({pathname: this.router.url})
                }
            }
        });
    };

EDIT: based on this comment re: passing paths in callback being a CSRF vulnerability:

Final working code in my auth.service.ts is:

import { Injectable }      from '@angular/core';
import { tokenNotExpired } from 'angular2-jwt/angular2-jwt';
import { Router, ActivatedRoute } from '@angular/router';
import { UUID } from 'angular2-uuid/index';

// Avoid name not found warnings
declare var Auth0Lock: any;

@Injectable()
export class Auth {
    // Configure Auth0
    lock = new Auth0Lock('Client_ID', 'Domain',{
        auth: {
            redirectUrl: location.origin + '/login',
            responseType: 'token',
        }
    });
    //Store profile object in auth class
    userProfile: Object;

    constructor(private router: Router, route: ActivatedRoute) {

        // Set userProfile attribute of already saved profile
        this.userProfile = JSON.parse(localStorage.getItem('profile'));

        // Add callback for lock `authenticated` event
        this.lock.on("authenticated", (authResult:any) => {
            localStorage.setItem('id_token', authResult.idToken);
            let pathname_object: any = JSON.parse(authResult.state);
            let pathname: any = localStorage.getItem(pathname_object.pathname_key);
            //get rid of localStorage of url
            localStorage.removeItem(pathname_object.pathname_key);
            //navigate to original url
            this.router.navigate([pathname], {});

        // Fetch profile information
        this.lock.getProfile(authResult.idToken, (error:any, profile:any) => {
            if (error) {
                // Handle error
                alert(error);
                return;
            }

            localStorage.setItem('profile', JSON.stringify(profile));
                this.userProfile = profile;
            });
        });
    }

    public login() {
        //generate UUID against which to store path
        let uuid = UUID.UUID();
        localStorage.setItem(uuid, this.router.url);
        // Call the show method to display the widget.
        this.lock.show({
            auth: {
                params: {
                    scope: 'openid name email',
                    state: JSON.stringify({pathname_key: uuid})
                }
            }
        });
    };
...
}
1

1 Answers

1
votes

In our auth service we do something something like:

const options: any = {
    auth: {
        redirectUrl: location.origin,
        responseType: 'token'
    },
};

constructor(private router: Router) {
    new Auth0Lock(environment.auth0ClientId, environment.auth0Domain, options);
    ...
    this.lock.on('authenticated', (authResult: any) => {
        ...
        this.router.navigateByUrl(authResult.state);
    });
}

public login() {
    this.lock.show({
        auth: {
            params: {state: this.router.url},
        }
    });
};