When a user accesses a URL without being authenticated we want to take them to the authentication page and then automatically redirect them to their requested URL after successful login. This is particularly important for following URLs from emails or other notifications, and for browser bookmarking.
Aurelia has a navigation pipeline which includes an authorization step and is executed for any navigation action. As part of an authorize step we can check whether the route is configured to require authentication and redirect the user to a login page (” in my case) if they are not.
class AuthorizeStep { protected run(navigationInstruction, next) { // determine if the route requires authorization // i.e. { route: '...', module: '...', setting: { auth: ... }} let authConfig = navigationInstruction.getAllInstructions().find(i => i.config.settings.auth); if (!authConfig) return next(); let isLoggedIn = checkIfLoggedIn(); if (!isLoggedIn) return next.cancel(new Redirect("")); // redirect to login page return next(); } }
To redirect after login we need to save the navigation instruction from the failed login and use it following the next successful login. To save it I introduced a NotAuthorizedRedirect
class.
import { autoinject } from "aurelia-framework"; import { Router, Redirect, NavigationInstruction } from "aurelia-router"; // singleton by virtue of aurelia's default DI @autoinject export class NotAuthorizedRedirect { private navigationInstruction: NavigationInstruction; constructor(private router: Router) { } public notAuthorized(from: NavigationInstruction) { this.navigationInstruction = from; return new Redirect(""); } public get redirecting() { return !!this.navigationInstruction; } public tryReturnTo(): string { if (!this.navigationInstruction) return ""; let uri = this.navigationInstruction.router.generate( this.navigationInstruction.config.name, Object.assign(this.navigationInstruction.params, this.navigationInstruction.queryParams), { replace: true }); this.navigationInstruction = null; // single use return uri; } }
We use this class by first calling the notAuthorized
method which saves the navigation instruction and returns a redirect to use. Once the user is known to be authorized we call tryReturnTo
to generate a new url.
One issue I had was that some parameters come in via the query string. Thankfully the router generate
method has a feature that any parameters given which are not in the route definition (i.e. the id
in a route-name/:id
) are applied as query string parameters, so tryReturnTo
is able to copy the query string parameters (.queryParams
) onto the other parameters to re-generate the query string.
We can now use these methods from the AuthorizeStep
as follows:
class AuthorizeStep { constructor(private redirect: NotAuthorizedRedirect) { } protected run(navigationInstruction, next) { let authConfig = navigationInstruction.getAllInstructions().find(i => i.config.settings.auth); if (!authConfig) return next(); let isLoggedIn = checkIfLoggedIn(); if (!isLoggedIn) return next.cancel(this.redirect.notAuthorized(navigationInstruction)); let redirectUri = this.redirect.tryReturnTo(); if (redirectUri) return next.cancel(new Redirect(redirectUri)); // return to url from before login attempt return next(); } }
A minor limitation of this approach is that the original URL applied by the user disappears from the address bar when they are redirected to log in. To remedy this NotAuthorizedRedirect
also has a little helper property, redirecting
, which can be used to indicate to the user that they are being redirected, and could easily be extended to show the user the original url.
The workings of this were originally posted in an aurelia discourse thread.