1

I have a view that can be accessed by a direct link from an email.

Ex. http://myServer:7747/#/pics/ClientId/YYYY-MM-DD

So this is set up using a route:

{ route: ['/pics', '/pics/:clientId/:sessionDate', 'pics'], 
  name: 'pics', moduleId: './views/pics', nav: false, title: 'Pictures', 
  auth: true, activationStrategy: activationStrategy.invokeLifecycle 
},

So if a client clicks on this link and is not logged in, I want the view to redirect to a login screen (I am using aurelia-authentication plugin) and then when it succeeds, I want it to return to this page using the same urlParams.

I have the redirect to the login page working, but getting back to this view is proving difficult. If I just try to use history.back() the problem is that the authentication plugin has pushed another navigationInstruction (loginRedirect) onto the history before I can do anything. If I just try to hard-code a 'go back twice' navigation I run into a problem when a user simply tries to log in fresh from the main page and there is no history.

Seems like this should be easier than it is, what am I doing wrong?

R. Richards
  • 24,603
  • 10
  • 64
  • 64
Bitfiddler
  • 3,942
  • 7
  • 36
  • 51

2 Answers2

0

I haven't used the aurelia-authentication plugin, but I can help with a basic technique you can use that makes this very easy. In your main.js file, set the root of your app to a "login" component. Within the login component, when the user has successfully authenticated, set the root of your app to a "shell" component (or any component you choose) that has a router view and configure the router in its view-model. Once this happens, the router will take the user to the proper component based on the url. If the user logs out, just set the app root back to the "login" component.

Here's some cursory code to attempt to convey the idea. I assume you're using the SpoonX plugin, but that's not really necessary. Just as long as you reset the root of your app when the user authenticates, it will work.

In main.js

.....
aurelia.start().then(() => aurelia.setRoot('login'));
.....

In login.js

import {AuthService} from 'aurelia-authentication';
import {Aurelia, inject} from 'aurelia-framework';

@inject(AuthService, Aurelia)
export class Login {
    constructor(authService, aurelia) {
        this.authService = authService;
        this.aurelia = aurelia;
    }

    login(credentialsObject) {
        return this.authService.login(credentialsObject)
            .then(() => {
                this.authenticated = this.authService.authenticated;

                if (this.authenticated) {
                    this.aurelia.setRoot('shell');
                }
            });
    }

    .....
}

In shell.html

.....
<router-view></router-view>
.....

In shell.js

.....
configureRouter(config, router) {
    this.router = router;
    config.map(YOUR ROUTES HERE);
}
.....
Jeff G
  • 1,996
  • 1
  • 13
  • 22
  • Hi Jeff, thanks for the help! I found similar solutions before but I had issues with menus not reloading properly (and yes I tried all sorts solutions from ServiceStack to reset, deactivate, etc... the router) this also doesn't solve the use case that I'm after. So to use your example, I have an email with a link to a view in the 'shell' app that has email specific urlParams, but when the user clicks the link and is not logged in, the user should be redirected to log in and then upon login they should return to that view with all the urlParms that were originally submitted. – Bitfiddler Mar 06 '17 at 17:17
0

I got this to work by replacing the plugin's authenticateStep with my own:

import { inject } from 'aurelia-dependency-injection';
import { Redirect } from 'aurelia-router';
import { AuthService } from "aurelia-authentication";
import { StateStore } from "./StateStore";

@inject(AuthService, StateStore)
export class SaveNavStep {
    authService: AuthService;
    commonState: StateStore;

    constructor(authService: AuthService, commonState: StateStore) {
        this.authService = authService;
        this.commonState = commonState;
    }

    run(routingContext, next) {
        const isLoggedIn = this.authService.authenticated;
        const loginRoute = this.authService.config.loginRoute;

        if (routingContext.getAllInstructions().some(route => route.config.auth === true)) {
            if (!isLoggedIn) {
                this.commonState.postLoginNavInstr = routingContext;
                return next.cancel(new Redirect(loginRoute));
            }
        } else if (isLoggedIn && routingContext.getAllInstructions().some(route => route.fragment === loginRoute)) {
            return next.cancel(new Redirect(this.authService.config.loginRedirect));
        }

        return next();
    }
}

The only difference between mine and the stock one is that I inject a 'StateStore' object where I save the NavigationInstruction that requires authentication.

Then in my login viewModel, I inject this same StateStore (singleton) object and do something like this to log in:

login() {
    var redirectUri = '#/defaultRedirectUri';

    if (this.commonState.postLoginNavInstr) {
        redirectUri = this.routing.router.generate(this.commonState.postLoginNavInstr.config.name,
                            this.commonState.postLoginNavInstr.params,
                            { replace: true });
    }
    var credentials = {
        username: this.userName,
        password: this.password,
        grant_type: "password"
    };
    this.routing.auth.login(credentials,
        { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } },
        redirectUri
    ).catch(e => {
        this.dialogService.open({
            viewModel: InfoDialog,
            model: ExceptionHelpers.exceptionToString(e)
        });
    });
}; 

Hope this helps someone!

Bitfiddler
  • 3,942
  • 7
  • 36
  • 51