To add a bit more detail, for the back end I’d use something pre-built. My preferences are typically:
If possible use a cloud based authentication/authorization provider like Auth0 or AzureAD. These should allow you to create users which have associated claims. Users should then be able to log in using either a pre-existing account like (Microsoft Account, Twitter, or similar) to acquire a JSON Web Token or JWT. Typically the JWT will store the claims (what the user has permission to do).
The workflow is as follows:
1. Aurelia login page posts to the authentication provider to acquire a token.
This would look something like:
logIn(userName, password){
return this.http.fetch('token', {
method: 'post',
body: json({name: userName, password : password})
})
.then(response => response.json())
.then(tokenResult => {
if(tokenResult.success) window.localStorage.setItem("token", tokenResult.token);
return tokenResult;
})
.catch(error => {
console.log('Error retrieving token');
});
}
In this example, I post the username and password which have been received from a login form view-model via data-binding. The workflow would be slightly different if you were using an identity provider.
2. The authorization header
This token would then typically be attached to the header on each request that requests authorization.
get tokenInterceptor(){
let auth = this;
return {
request(request) {
let token = auth.getToken();
if(token){
request.headers.append('authorization', `bearer ${token}`);
}
return request;
}
}
}
The back end would then receive the request with the authorization header and verify that the that the token hadn’t been tampered with, and that the user had access to the specified resource. The back end module responsible for performing this verification would either be something like ASP.NET Core identity if you want to build it yourself, or one of the authentication providers I mentioned earlier.
3. Redirecting the user client side
The other piece of the puzzle is determining whether you should allow users to visit a page client side. You can do this by decoding the token, examining the claims, and either showing the page, or redirecting the user to an appropriate error page.
You can do this by implementing an authorization step in the routing pipeline, which would look something like this:
import {Redirect} from 'aurelia-router';
export class AuthorizeStep {
constructor(authService){
this.authService = authService;
}
run(navigationInstruction, next) {
if (navigationInstruction.getAllInstructions().some(i => i.config.settings.auth)) {
if (!this.authService.isLoggedIn()) {
return next.cancel(new Redirect('login'));
}
}
return next();
}
}
4. Hiding navigation links
If you want to take it a step further, you could then also decide which links a user should see based on their claims using a value converter.
If you’re interested you can check out a complete sample implementation of this authentication pattern here.