Angular Guards | Angular Auth Guard | Angular Canactivate
In this post you will learn how to protect your pages inside Angular by using Angular Guards.
Client side protection
Just from the beginning I must tell you the truth. You can't really protect your application in client side. Which actually means it doesn't really matter what we write inside guards, if somebody really wants to break our code and access the page it is totally possible.
You must do all needed backend checks and allow people to access your API only when they have correct credentials.
Which actually means that your application must be first safe on the backend and guards in Angular just help users to avoid accident page access. It is not to make your application bulletproof.
Creating a guard
So let's try to protect our pages now.
As you can see here I already prepared a small Angular application. We have 2 routes: Home and Private.
So inside Angular to protect our routes we are using guards.
Guard is a class with a method which will be called when we try to access our page.
If this method returns true then we can access the page. In other case we can't.
Let's create our guard now.
// src/app/auth-guard.service.ts
@Injectable()
export class AuthGuardService implements CanActivate {}
This is just a service which implements CanActivate
.
Now we directly get an error that our service didn't implement a method correctly.
@Injectable()
export class AuthGuardService implements CanActivate {
canActivate(): boolean {
return true;
}
}
This method simply returns a boolean
and in order to check our guard I returned here true
.
@NgModule({
providers: [AuthGuardService],
})
export class AppModule {}
Now we registered a guard as a service inside our module.
And the last thing is to add a guard to the page that we want to protect.
// src/app/private/private.module.ts
const routes: Routes = [
{
path: 'private',
component: PrivateComponent,
canActivate: [AuthGuardService],
},
];
If we check in browser we still can access our private
route because our canActivate
always returns true. If we change it to false
we won't be able to access it.
Adding local storage check
If you have some simple case with data on the client and you don't need to make an API call then you can simply write synchronous canActivate
. I want to add check of localStorage
property to our method to allow access to the page.
canActivate(): boolean {
if (localStorage.getItem('token')) {
return true;
}
return false;
}
Here we check for token
in our localStorage. If we have such property then we allow access to the page. In other case we don't.
If we try to access the page in browser it won't work. But if we add a localStorage key token
with any value then we are allowed to access the page.
Async guards in Angular
But the problem is that this case is rarely used. In the real application we fetch a current user to make sure that we can access the page. Which means that our canActivate
method must to asynchronous.
In our application I already prepared an additional CurrentUserService
which has a stream of user data.
// app/currentUser.service.ts
@Injectable()
export class CurrentUserService {
currentUser$ = new BehaviorSubject<
{ id: string; name: string } | null | undefined
>(undefined);
setCurrentUser() {
if (localStorage.getItem('token')) {
this.currentUser$.next({ id: '1', name: 'Foo' });
} else {
this.currentUser$.next(null);
}
}
}
We have here a currentUser$
stream of data and a setCurrentUser
function which will emulate real API call like in production application. We need to call setCurrentUser
on initialize of our application in order to know if we are logged in user or not. Here we just return some mock data if we set a token
inside localStorage.
// app/app.component.ts
export class AppComponent implements OnInit {
constructor(private currentUserService: CurrentUserService) {}
ngOnInit(): void {
setTimeout(() => {
this.currentUserService.setCurrentUser();
}, 2000);
}
}
Here we called setCurrentUser
on initialize with 2 seconds delay to simulate API call which takes some time.
Now we must update our canActivate
function and use currentUser$
stream inside.
export class AuthGuardService implements CanActivate {
constructor(
private currentUserService: CurrentUserService,
private router: Router
) {}
canActivate(): Observable<boolean> {
return this.currentUserService.currentUser$.pipe(
filter((currentUser) => currentUser !== undefined),
map((currentUser) => {
if (!currentUser) {
this.router.navigateByUrl('/');
return false;
}
return true;
})
);
}
}
First of all here we returned Observable<boolean>
and not just boolean
. It means that our canActivate
is async now. Inside we must return a stream which returns either true
or false
.
As our initial value is undefined
inside the stream we must filter it our. So we are getting either null
or current user object.
Additionally we redirect to the homepage if the user is not logged in.
As you can see in browser we can access the route but only after 2 seconds. It happens because we want for the returns from asynchronous canActivate
. If we are jumping directly to /private
page we won't see any content until we get currentUser. If we don't get it we are redirected to the homepage.
Want to conquer your next JavaScript interview? Download my FREE PDF - Pass Your JS Interview with Confidence and start preparing for success today!
📚 Source code of what we've done