Check if the user logged in on any page change in Angular 2
I'm showing you simple implementation with Angular2. You can take advantage of @CanActivate hook as shown blow to check whether the user is loggedIn or not with isLoggedIn function which returns promise.
NOTE: below implementation is to check whether user is loggedIn
before accessing any component or not. I hope by some modification you can achieve what you want to have.
Auth.ts
import {Observable} from 'rxjs/Observable';
export class Auth {
constructor() {
this.loggedIn = false;
}
login() {
this.loggedIn = true;
}
logout() {
this.loggedIn = false;
}
check() {
return Observable.of(this.loggedIn);
}
}
isLoggedIn.ts
import {Injector} from 'angular2/core';
import {appInjector} from './appInjector';
import {Auth} from './Auth';
import {Router, ComponentInstruction} from 'angular2/router';
export const isLoggedIn = (next: ComponentInstruction, previous: ComponentInstruction) => {
let injector: Injector = appInjector(); // get the stored reference to the injector
let auth: Auth = injector.get(Auth);
let router: Router = injector.get(Router);
// return a boolean or a promise that resolves a boolean
return new Promise((resolve) => {
auth.check()
.subscribe((result) => {
if (result) {
resolve(true);
} else {
router.navigate(['/Login']);
resolve(false);
}
});
});
};
appInjector.ts
import {Injector} from 'angular2/core';
let appInjectorRef: Injector;
export const appInjector = (injector?: Injector):Injector => {
if (injector) {
appInjectorRef = injector;
}
return appInjectorRef;
};
somecomponent.ts
import {Component, View,ViewChild} from 'angular2/core';
import {CanActivate} from 'angular2/router';
import {isLoggedIn} from './isLoggedIn';
@Component({
selector: 'some',
template: 'some text'
})
@CanActivate((next: ComponentInstruction, previous: ComponentInstruction) => {
return isLoggedIn(next, previous); // this will tell whether user is loggedIn or not.
})
export class Protected {
}
boot.ts
.
.
import { provide, ComponentRef } from 'angular2/core';
import { appInjector } from './app-injector';
.
.
bootstrap(AppComponent, [...]).then((appRef: ComponentRef) => {
// store a reference to the application injector
appInjector(appRef.injector);
});
There are two ways to restrict access Custom Router Outlet
and CanActivate Decorator
shown and implemented in this great article Authentication in Angular 2
This is what I did, used canActive property in app.routing.ts
{
path: 'dashboard',
loadChildren: './dashboard',
canActivate:[AuthGuard]
},
Please follow the below 5 minutes video tutorial
https://www.youtube.com/watch?v=0Qsg8fyKwO4
Note: This solution works fine for Angular 4
I think extending RouterOutlet is a common way to achive this
Example posted a while ago in Gitter by CaptainCodeman (not tested myself yet)
import {Directive, Injector, Attribute, ElementRef, DynamicComponentLoader} from 'angular2/core';
import {Router, RouteData, RouterOutlet, RouteParams, Instruction, ComponentInstruction} from 'angular2/router';
/*
Example implementation
Given a route:
@RouteConfig([
{ path: '/thing/:id', component: ThingComponent, name: 'Thing', data: { public: false, roles:['thinger'] } }
])
authorize(instruction: ComponentInstruction):boolean {
// simplest case - route is public
if (<boolean>instruction.routeData.data['public']) {
return true;
}
// if not public then we at least need an authenticated user
if (this.isAuthenticated()) {
var routeRoles = <any[]>instruction.routeData.data['roles'];
var userRoles = <string[]>this.roles();
// no roles required for route = user just needs to be authenticated
var authorized = routeRoles.length === 0 || routeRoles.some(routeRole => userRoles.indexOf(routeRole) >= 0);
return authorized;
}
return false;
}
*/
export abstract class IAuthService {
abstract isAuthenticated():boolean;
authorize(instruction: ComponentInstruction, params:any):boolean {
// authorized if route allows public access or user is authenticated
return this.isAuthenticated() || <boolean>instruction.routeData.data['public']
}
}
@Directive({selector: 'secure-outlet'})
export class SecureRouterOutlet extends RouterOutlet {
signin:string;
unauthorized:string;
injector:Injector;
private parentRouter: Router;
private authService: IAuthService;
constructor(_elementRef: ElementRef, _loader: DynamicComponentLoader,
_parentRouter: Router, @Attribute('name') nameAttr: string,
authService:IAuthService,
injector:Injector,
@Attribute('signin') signinAttr: string,
@Attribute('unauthorized') unauthorizedAttr: string) {
super(_elementRef, _loader, _parentRouter, nameAttr);
this.parentRouter = _parentRouter;
this.authService = authService;
this.injector = injector;
this.signin = signinAttr;
this.unauthorized = unauthorizedAttr;
}
activate(nextInstruction: ComponentInstruction): Promise<any> {
var params = this.getAllRouteParams(this.injector);
var isAuthorized = this.authService.authorize(nextInstruction, params);
if (isAuthorized) {
return super.activate(nextInstruction);
}
if (this.authService.isAuthenticated()) {
var ins = this.parentRouter.generate([this.unauthorized]);
return super.activate(ins.component);
} else {
var ins = this.parentRouter.generate([this.signin,{url:location.pathname}]);
return super.activate(ins.component);
}
}
reuse(nextInstruction: ComponentInstruction): Promise<any> {
return super.reuse(nextInstruction);
}
getAllRouteParams(injector) {
let params = null;
while(injector) {
const routeParams = injector.getOptional(RouteParams);
if (routeParams) {
if (params === null) {
params = {};
} else {
params = Object.create(params);
}
Object.assign(params, routeParams.params);
}
injector = injector.parent;
}
return params;
}
}
If you use routing (and it seems to be the case since you say: "on every page change"), you can leverage several things:
Create a custom router-outlet (a sub class of
RouterOutlet
) that checks authentication with itsactivate
method is called. In this case, you can have something global. Something like that:@Directive({ selector: 'auth-outlet' }) export class AuthOutlet extends RouterOutlet { (...) activate(oldInstruction: ComponentInstruction) { var url = this.parentRouter.lastNavigationAttempt; if (isAuthenticated()) { return super.activate(oldInstruction); } else { (...) } } }
See this question for more details:
- Angular 2 Authentication with child routes
Leverage the
CanActivate
decorator to check is a component can be activated or not. In your case, you can execute authentication checking at this level.Something could also be done at the RouterLink level to show / hide route links. In this case, you can apply roles on these links based on related route configuration and current user hints. See this question for more details:
- Angular2. How to hide(no-render) the link in the menu after check access?
This can be also handled within an HTTP interceptor (a class that extends the Http
one). In this case, when a request is executing, you can plug some authentication checks:
@Injectable()
export class CustomHttp extends Http {
constructor(backend: ConnectionBackend, defaultOptions: RequestOptions) {
super(backend, defaultOptions);
}
request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
console.log('request...');
if (isAuthenticated()) {
return super.request(url, options).catch(res => {
// do something
});
} else {
// Redirect to login page
// Or throw an exception: return Observable.throw(new Error(...));
}
}
(...)
}
See this question for more details:
- How to create interceptors in Angular2?