Angular 2 security: redirect to clicked route after login? - authentication

I'm building my first app using the release version of Angular 2 (I'm on 2.1.0 currently). I have set up a Route Guard, and I am using it in my routing.ts to secure one of the routes with authentication. When clicked, if not logged in, it redirects the user to the login route. There they can login, and if authenticated, it sets a localStorage token. Here's where I have a problem. I want to then redirect the user to the route they clicked on initially before they were redirected to the login, but I can't figure out how to get the clicked route once they hit the Guard canActivate method, or on the login. This seems like a fairly common usage scenario, but I can't find any examples of doing this.

Ok this is my stripped out example which should illustrate the point:
#Injectable()
export class AuthGuardService implements CanActivate
{
toUrl;
constructor(public authenticationService: AuthenticationService,
public router: Router)
{
}
canActivate(route, state): boolean
{
this.toUrl = state.url; //This is the url where its going
if (this.authenticationService.isLoggedIn()) return true;
this.router.navigate(['/login', {redirect: this.toUrl}]);
}
}
and in the login component use the ngOnInit to check for any redirect ulrs:
export class LoginComponent
{
redirect;
constructor(
private authenticationService: AuthenticationService,
private route: ActivatedRoute,
private router: Router)
{
}
ngOnInit()
{
this.redirect = this.route.snapshot.params['redirect'];
}
logIn(): void
{
this.authenticationService
.login(this.searchParams)
.subscribe(
() =>
{
this.logInSuccess();
},
error =>
{
this.logInFail(error)
})
}
logInSuccess(): void
{
if (this.redirect)
{
this.router.navigateByUrl(this.redirect);
}
else
{
this.router.navigate([''])
}
}
}

Related

Protect routes in NextJS using Firebase Authentication

The route I want to protect: /account
If the user is NOT authenticated, then redirect to /signIn
Having an SSR NextJS project, and working with Firebase authentication, how can I achieve a production battle-tested proper protected routes?
The example provided on NextJS docs is not working right now:
with-firebase-auth
So I submitted an issue:
with-firebase-auth-example-not-working
Add to that that I'm new to NextJs and also, unfortunately, I've never used JWT :( or any sort of backend protected routes cookies/JWT/sessions implementation....Until now that I want/need it.
What sort of workaround I've tried, well, something like this:
import Account from "./Account.js";
import Loading from "./Loading.js";
import { useRequireAuth } from "./use-require-auth.js";
function Account(props) {
const auth = useRequireAuth();
// If auth is null (still fetching data)
// or false (logged out, above hook will redirect)
// then show loading indicator.
if (!auth) {
return <Loading />;
}
return (
<Account auth={auth} />
);
}
// Hook (use-require-auth.js)
import { useEffect } from "react";
import { useAuth } from "./use-auth.js";
import { useRouter } from "./use-router.js";
function useRequireAuth(redirectUrl = '/sigIn'){
const auth = useAuth();
const router = useRouter();
// If auth.user is false that means we're not
// logged in and should redirect.
useEffect(() => {
if (auth.user === false){
router.push(redirectUrl);
}
}, [auth, router]);
return auth;
}
But this is all happening on the client-side....the server is not checking anything.
I'm gonna a post a very basic answer to this. I dunno how you're going to check if a user is authenticated on firebase. My own code uses AWS Cognito for this purpose.
We' are going to put that piece of code at the end of the page. By doing so, if the user is not authenticated we will redirect the user to the sign in page.
export async function isAuthenticated(context) {
// your code to check firebase authentication
// return true if not authenticated, else return false
// Maybe this way
var user = firebase.auth().currentUser;
if (user)
return false;
else
return true;
}
export const getServerSideProps: GetServerSideProps = async (ctx) => {
let shouldRedirect = await isAuthenticated(ctx);
if (shouldRedirect) {
return {
redirect: {
destination: '/sign-in',
permanent: false
}
}
}
return {
props: {}
}
}
export default Account;
That's it. Now the route is protected through SSR.

How to return to view after authentication in Aurelia

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?
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);
}
.....
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!

Angular2 auth guard with http request and observables

i am currently implementing an angular2 example application with spring boot as backend. I am having some problems with the frontend auth guard mechanism and observables.
I am trying to achieve:
when someone enters a protected route the auth guard should check if a user
is already set in the auth service variable
if it is not set then a http request should be issued to check if a session is available
the service method should return a true/false value (asynchronously because of the possible http request)
if service returns false the auth guard should redirect to login page
auth guard should return true/false so the route can either be activated or not
My code currently looks like this (i am using RC5 btw.):
Auth Guard
import {Injectable} from "#angular/core";
import {CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router} from "#angular/router";
import {Observable, Subject} from "rxjs/Rx";
import {AuthService} from "./auth.service";
#Injectable()
export class AuthGuard implements CanActivate {
constructor(private authService: AuthService, private router: Router) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
var authenticated = this.authService.isAuthenticated();
var subject = new Subject<boolean>();
authenticated.subscribe(
(res) => {
console.log("onNext guard: "+res);
if(!res && state.url !== '/signin') {
console.log("redirecting to signin")
this.router.navigate(['/signin']);
}
subject.next(res);
});
return subject.asObservable();
}
}
Auth Service
import {Injectable} from "#angular/core";
import {User} from "./user.interface";
import {Router} from "#angular/router";
import {Http, Response, Headers} from "#angular/http";
import {environment} from "../environments/environment";
import {Observable, Observer, Subject} from "rxjs/Rx";
#Injectable()
export class AuthService {
private authenticatedUser : User;
constructor(private router: Router, private http: Http) {}
signupUser(user: User) {
}
logout() {
//do logout stuff
this.router.navigate(['/signin']);
}
isAuthenticated() : Observable<boolean> {
var subject = new Subject<boolean>();
if (this.authenticatedUser) {
subject.next(true);
} else {
this.http.get(environment.baseUrl + '/user')
.map((res : Response) => res.json())
.subscribe(res => {
console.log("next: returning true");
this.authenticatedUser = User.ofJson(res);
subject.next(true);
}, (res) => {
console.log("next: returning false");
subject.next(false);
});
}
return subject.asObservable();
}
}
The problem is: the guard never allows the router component to activate, even though when i am logged in.
Thanks for the help!
Change
return subject.asObservable();
to
return subject.asObservable().first();
The router waits for the observable to complete. first() makes it complete after the first event.

Handle a request with a specific function in a component

We are developing a component that handles OpenID Connect's implicit flow.
In step 5 of the flow, the "Authorization Server sends the End-User back to the Client with an ID Token and, if requested, an Access Token." We would like our component to handle that request, which will be to ~/openid-login.
How do we configure Aurelia to have it route to a function in our component?
export class OpenId {
// how do we route ~/openid-login to this?
public handleRequest() {
}
}
Note: Here is the work in progress.
Using a navStrategy within your routeConfig will allow you to do what ever you like before navigating to a page. See below:
import { autoinject } from 'aurelia-framework';
import { RouterConfiguration, Router, NavigationInstruction } from 'aurelia-router';
#autoinject
export class App {
router: Router;
configureRouter(config: RouterConfiguration, router: Router) {
let openIdNavStrat = (instruction: NavigationInstruction) => {
console.log('Do whatever we would like to do.');
// then redirect to where ever you would like.
instruction.config.moduleId = 'login';
}
config.map([
{ route: ['', 'login'], moduleId: 'login' },
{ route: 'openid-login', navigationStrategy: openIdNavStrat },
]);
this.router = router;
}
}
There is documentation on Navigation Strategies here: http://aurelia.io/hub.html#/doc/article/aurelia/router/latest/router-configuration/3

Pass data to component after it has finished routing

I have a navbar-login (login form in navbar) and a page-login (a page with login-form which can be routed to). The navbar-login-form and the page-login-form are two-way-binded via a service (see first codebit below).
What I want is the following flow:
User enters email and password in navbar-login
On Clicking Submit Button, the credentials are sent to a login.service
If credentials are wrong, the service routes to the login-page with the credentials displayed
The two-way-binding with the service works fine if the page-login is already displayed. But if it wasn't displayed and I enter credentials and hit the button, it only routes to page-login but does not pass the credentials.
I'm using the following service to have navbar-login and page-login communicate with each other (two-way-binding across "unrelated" components):
export class LoginNav2PageService {
viewerChange: EventEmitter<{email:string,password:string}> = new EventEmitter();
constructor() {}
emitNavChangeEvent(user:{email:string,password:string}) {
this.viewerChange.emit(user);
}
getNavChangeEmitter() {
return this.viewerChange;
}
}
This is the navbar component, pass2page is hooked with a keyup event in the HTML inputs:
export class LoginNavbarComponent {
user:= {email:'',password:''};
constructor(private _loginNav2pageService:LoginNav2PageService, private _loginService:LoginService){}
pass2page(){
this._loginNav2pageService.emitNavChangeEvent(this.user);
}
onNavbarLoginBtn(){
this._loginService.onNavbarLogin(this.user);
}
}
And this is the listener in the page-login component:
export class LoginPageComponent implements OnInit{
user= {email:"", password:""};
subscription:any;
constructor(private _loginNav2pageService:LoginNav2PageService){}
ngOnInit():any{
this.subscription = this._loginNav2pageService.getNavChangeEmitter().subscribe(user => this.setViewer(user));
}
setViewer(user:{email:string, password:string}){
this.user = user;
}
}
And finally the loginService:
export class LoginService{
constructor(private _router:Router, private _loginNav2pageService:LoginNav2PageService){}
//login User from Navbar
onNavbarLogin(user:LoginUserInterface){
//login and routing if successful
if(user.email === 'name' && user.password === '1234'){
console.log("Login Success");
//route to platform
}
//else route to login page to show validation errors to users
else {
this._router.navigate(['Login']);
this._loginNav2pageService.emitNavChangeEvent(user);
console.log("wrong credentials");
}
}
}
after a good nights sleep I figured it out :), the code above was a snippet i adapted before in some other parts and way to complicated for this....
Now i'm simply using the ngAfterViewInit Lifecycle Hook to get the data from the service.
Thanks!