Skip to main content
Version: 6.0

Protect the Routes

Overview

Now that we are authenticating with a provider we need to look at protecting our routes. This protection takes two major forms:

  1. Guarding our routes so a user cannot navigate to various places within our application unless they are logged in.
  2. Protecting our backend API such that users cannot access data without a valid access token. Our role is to pass the access token to our API.

We will also see how to handle the possibility that our APIs may now issue 401 errors in cases where our access token has expired or is otherwise invalid.

We will build upon the application we created in the getting started tutorial in order to implement route guards for our application's routes as well as to add HTTP interceptors to attach access tokens to outgoing requests and to handle potential 401 errors in responses.

Let's Code

As mentioned previously, this tutorial builds upon the application created when doing the getting started tutorial. If you have the code from when you performed that tutorial, then you are good to go. If you need the code you can make a copy from our GitHub repository.

Route Guards

We are using the Tab1Page to manage our authentication status. Let's assume that the Tab2Page and Tab3Page should only be accessible if the user is authenticated. We already have a method that determines if the user is authenticated or not. In its most basic form, we use the existence of an AuthResult with an access token to determine whether or not we are authenticated. Other tutorials show how this can be expanded.

src/app/core/authentication.service.ts

_82
import { Injectable } from '@angular/core';
_82
import {
_82
Auth0Provider,
_82
AuthConnect,
_82
AuthResult,
_82
ProviderOptions,
_82
} from '@ionic-enterprise/auth';
_82
import { Capacitor } from '@capacitor/core';
_82
import { SessionService } from './session.service';
_82
!!authResult && (await AuthConnect.isAccessTokenAvailable(authResult))

We will use an Angular CanActivate guard to protect those routes. Generate the guard via npx ng generate guard core/guards/auth:

Terminal

_10
npx ng generate guard core/guards/auth
_10
? Which type of guard would you like to create? (Press <space> to select, <a> to toggle all, <i> to invert selection, and <enter> to proceed)
_10
❯◉ CanActivate
_10
◯ CanActivateChild
_10
◯ CanDeactivate
_10
◯ CanMatch
_10
CREATE src/app/core/guards/auth.guard.spec.ts (482 bytes)
_10
CREATE src/app/core/guards/auth.guard.ts (133 bytes)

Now that the guard has been generated, we need to build it out.

src/app/tabs/tabs.routes.ts
src/app/core/guards/auth.guard.ts

_36
import { Routes } from '@angular/router';
_36
import { TabsPage } from './tabs.page';
_36
_36
export const routes: Routes = [
_36
{
_36
path: 'tabs',
_36
component: TabsPage,
_36
children: [
_36
{
_36
path: 'tab1',
_36
loadComponent: () =>
_36
import('../tab1/tab1.page').then((m) => m.Tab1Page),
_36
},
_36
{
_36
path: 'tab2',
_36
loadComponent: () =>
_36
import('../tab2/tab2.page').then((m) => m.Tab2Page),
_36
},
_36
{
_36
path: 'tab3',
_36
loadComponent: () =>
_36
import('../tab3/tab3.page').then((m) => m.Tab3Page),
_36
},
_36
{
_36
path: '',
_36
redirectTo: '/tabs/tab1',
_36
pathMatch: 'full',
_36
},
_36
],
_36
},
_36
{
_36
path: '',
_36
redirectTo: '/tabs/tab1',
_36
pathMatch: 'full',
_36
},
_36
];

For this application, we want to guard the tab2 and tab3 routes. We cannot guard tab1 because we are using that one to log in.

src/app/tabs/tabs.routes.ts
src/app/core/guards/auth.guard.ts

_39
import { Routes } from '@angular/router';
_39
import { TabsPage } from './tabs.page';
_39
import { authGuard } from '../core/guards/auth.guard';
_39
_39
export const routes: Routes = [
_39
{
_39
path: 'tabs',
_39
component: TabsPage,
_39
children: [
_39
{
_39
path: 'tab1',
_39
loadComponent: () =>
_39
import('../tab1/tab1.page').then((m) => m.Tab1Page),
_39
},
_39
{
_39
path: 'tab2',
_39
loadComponent: () =>
_39
import('../tab2/tab2.page').then((m) => m.Tab2Page),
_39
canActivate: [authGuard],
_39
},
_39
{
_39
path: 'tab3',
_39
loadComponent: () =>
_39
import('../tab3/tab3.page').then((m) => m.Tab3Page),
_39
canActivate: [authGuard],
_39
},
_39
{
_39
path: '',
_39
redirectTo: '/tabs/tab1',
_39
pathMatch: 'full',
_39
},
_39
],
_39
},
_39
{
_39
path: '',
_39
redirectTo: '/tabs/tab1',
_39
pathMatch: 'full',
_39
},
_39
];

Import the authGuard and apply it to those two routes.

src/app/tabs/tabs.routes.ts
src/app/core/guards/auth.guard.ts

_10
import { CanActivateFn } from '@angular/router';
_10
_10
export const authGuard: CanActivateFn = (route, state) => {
_10
return true;
_10
};

We can still navigate to the tab2 and tab3 routes because our guard always returns true.

src/app/tabs/tabs.routes.ts
src/app/core/guards/auth.guard.ts

_10
import { inject } from '@angular/core';
_10
import { CanActivateFn } from '@angular/router';
_10
import { AuthenticationService } from '../authentication.service';
_10
_10
export const authGuard: CanActivateFn = async (route, state) => {
_10
const authService = inject(AuthenticationService);
_10
_10
return true;
_10
};

We need to make our guard async and inject the AuthenticationService.

src/app/tabs/tabs.routes.ts
src/app/core/guards/auth.guard.ts

_13
import { inject } from '@angular/core';
_13
import { CanActivateFn } from '@angular/router';
_13
import { AuthenticationService } from '../authentication.service';
_13
_13
export const authGuard: CanActivateFn = async (route, state) => {
_13
const authService = inject(AuthenticationService);
_13
_13
if (await authService.isAuthenticated()) {
_13
return true;
_13
}
_13
_13
return false;
_13
};

We can then check the user's authentication status. We want to return true if the user is authenticated. Otherwise we want to return false.

For this application, we want to guard the tab2 and tab3 routes. We cannot guard tab1 because we are using that one to log in.

Import the authGuard and apply it to those two routes.

We can still navigate to the tab2 and tab3 routes because our guard always returns true.

We need to make our guard async and inject the AuthenticationService.

We can then check the user's authentication status. We want to return true if the user is authenticated. Otherwise we want to return false.

src/app/tabs/tabs.routes.ts
src/app/core/guards/auth.guard.ts

_36
import { Routes } from '@angular/router';
_36
import { TabsPage } from './tabs.page';
_36
_36
export const routes: Routes = [
_36
{
_36
path: 'tabs',
_36
component: TabsPage,
_36
children: [
_36
{
_36
path: 'tab1',
_36
loadComponent: () =>
_36
import('../tab1/tab1.page').then((m) => m.Tab1Page),
_36
},
_36
{
_36
path: 'tab2',
_36
loadComponent: () =>
_36
import('../tab2/tab2.page').then((m) => m.Tab2Page),
_36
},
_36
{
_36
path: 'tab3',
_36
loadComponent: () =>
_36
import('../tab3/tab3.page').then((m) => m.Tab3Page),
_36
},
_36
{
_36
path: '',
_36
redirectTo: '/tabs/tab1',
_36
pathMatch: 'full',
_36
},
_36
],
_36
},
_36
{
_36
path: '',
_36
redirectTo: '/tabs/tab1',
_36
pathMatch: 'full',
_36
},
_36
];

Test this in your app. You should see that you cannot navigate to Tab2Page or Tab3Page unless you are authenticated. This is exactly what we want and it works well.

Run the application in a web browser and navigate directly to http://localhost:8100/tabs/tab2 while not authenticated. The result will be a white screen. We cannot navigate to the path, but we also don't have an existing route upon which to remain. We need to navigate somewhere. Let's add some code to navigate to the Tab1Page in cases such as this.

src/app/core/guards/auth.guard.ts

_16
import { inject } from '@angular/core';
_16
import { CanActivateFn } from '@angular/router';
_16
import { NavController } from '@ionic/angular';
_16
import { AuthenticationService } from '../authentication.service';
_16
_16
export const authGuard: CanActivateFn = async (route, state) => {
_16
const authentication = inject(AuthenticationService);
_16
const navigation = inject(NavController);
_16
_16
if (await authentication.isAuthenticated()) {
_16
return true;
_16
}
_16
_16
navigation.navigateRoot('/tabs/tab1');
_16
return false;
_16
};

Now when navigating directly to http://localhost:8100/tabs/tab2 while not authenticated, the user will be redirected to Tab1Page to authenticate. Note that this seems like something that only applies when running in the web and not a problem for our native application. This could be an issue for our native app, however, if it is using deep links or if our default route is protected.

Provide the Access Token

When a user logs in using Auth Connect the application receives an AuthResult that represents the authentication session. The AuthResult object provides access to several types of tokens:

  • ID Token: The ID token contains information pertaining to the identity of the authenticated user. The information within this token is typically consumed by the client application.
  • Access Token: The access token signifies that the user has properly authenticated. This token is typically sent to the application's backend APIs as a bearer token. The token is verified by the API to grant access to the protected resources exposed by the API. Since this token is used in communications with the backend API, a common security practice is to give it a very limited lifetime.
  • Refresh Token: Since access tokens typically have a short lifetime, longer lived refresh tokens are used to extend the length of a user's authentication session by allowing the access tokens to be refreshed.

The most common way for the backend API to protect its routes is to require that requests include a valid access token. As such, we are going to have to send the access token with each request.

src/app/core/authentication.service.ts
src/app/tab1/tab1.page.ts
src/app/tab1/tab1.page.html

_87
import { Injectable } from '@angular/core';
_87
import {
_87
Auth0Provider,
_87
AuthConnect,
_87
AuthResult,
_87
ProviderOptions,
_87
} from '@ionic-enterprise/auth';
_87
import { Capacitor } from '@capacitor/core';
_87
import { SessionService } from './session.service';
_87
_87
@Injectable({
_87
providedIn: 'root',
_87
})
_87
export class AuthenticationService {
_87
private authOptions: ProviderOptions;
_87
private provider: Auth0Provider;
_87
_87
constructor(private session: SessionService) {
_87
const isNative = Capacitor.isNativePlatform();
_87
this.provider = new Auth0Provider();
_87
this.authOptions = {
_87
audience: 'https://io.ionic.demo.ac',
_87
clientId: 'yLasZNUGkZ19DGEjTmAITBfGXzqbvd00',
_87
discoveryUrl:
_87
'https://dev-2uspt-sz.us.auth0.com/.well-known/openid-configuration',
_87
logoutUrl: isNative
_87
? 'io.ionic.acdemo://auth-action-complete'
_87
: 'http://localhost:8100/auth-action-complete',
_87
redirectUri: isNative
_87
? 'io.ionic.acdemo://auth-action-complete'
_87
: 'http://localhost:8100/auth-action-complete',
_87
scope: 'openid offline_access email picture profile',
_87
};
_87
}
_87
_87
initialize(): Promise<void> {
_87
const isNative = Capacitor.isNativePlatform();
_87
return AuthConnect.setup({
_87
platform: isNative ? 'capacitor' : 'web',
_87
logLevel: 'DEBUG',
_87
ios: {
_87
webView: 'private',
_87
},
_87
web: {
_87
uiMode: 'popup',
_87
authFlow: 'PKCE',
_87
},
_87
});
_87
}
_87
_87
async getAccessToken(): Promise<string | undefined> {
_87
const res = await this.getAuthResult();
_87
return res?.accessToken;
_87
}
_87
_87
async isAuthenticated(): Promise<boolean> {
_87
const authResult = await this.getAuthResult();
_87
return (
_87
!!authResult && (await AuthConnect.isAccessTokenAvailable(authResult))
_87
);
_87
}
_87
_87
async login(): Promise<void> {
_87
const authResult = await AuthConnect.login(this.provider, this.authOptions);
_87
this.saveAuthResult(authResult);
_87
}
_87
_87
async logout(): Promise<void> {
_87
const authResult = await this.getAuthResult();
_87
if (authResult) {
_87
await AuthConnect.logout(this.provider, authResult);
_87
this.saveAuthResult(null);
_87
}
_87
}
_87
_87
private async getAuthResult(): Promise<AuthResult | null> {
_87
return this.session.getSession();
_87
}
_87
_87
private async saveAuthResult(authResult: AuthResult | null): Promise<void> {
_87
if (authResult) {
_87
await this.session.setSession(authResult);
_87
} else {
_87
await this.session.clear();
_87
}
_87
}
_87
}

Add a method to the AuthenticationService that gets the access token:

src/app/core/authentication.service.ts
src/app/tab1/tab1.page.ts
src/app/tab1/tab1.page.html

_38
import { CommonModule } from '@angular/common';
_38
import { Component, OnInit } from '@angular/core';
_38
import { IonicModule } from '@ionic/angular';
_38
import { ExploreContainerComponent } from '../explore-container/explore-container.component';
_38
import { AuthenticationService } from './../core/authentication.service';
_38
_38
@Component({
_38
selector: 'app-tab1',
_38
templateUrl: 'tab1.page.html',
_38
styleUrls: ['tab1.page.scss'],
_38
standalone: true,
_38
imports: [CommonModule, IonicModule, ExploreContainerComponent],
_38
})
_38
export class Tab1Page implements OnInit {
_38
authenticated = false;
_38
accessToken: string | undefined = '';
_38
_38
constructor(private authentication: AuthenticationService) {}
_38
_38
ngOnInit() {
_38
this.checkAuthentication();
_38
}
_38
_38
async login(): Promise<void> {
_38
await this.authentication.login();
_38
this.checkAuthentication();
_38
}
_38
_38
async logout(): Promise<void> {
_38
await this.authentication.logout();
_38
this.checkAuthentication();
_38
}
_38
_38
private async checkAuthentication(): Promise<void> {
_38
this.authenticated = await this.authentication.isAuthenticated();
_38
this.accessToken = await this.authentication.getAccessToken();
_38
}
_38
}

Modify the Tab1Page to grab the access token.

src/app/core/authentication.service.ts
src/app/tab1/tab1.page.ts
src/app/tab1/tab1.page.html

_20
<ion-header [translucent]="true">
_20
<ion-toolbar>
_20
<ion-title> Tab 1 </ion-title>
_20
</ion-toolbar>
_20
</ion-header>
_20
_20
<ion-content [fullscreen]="true">
_20
<ion-header collapse="condense">
_20
<ion-toolbar>
_20
<ion-title size="large">Tab 1</ion-title>
_20
</ion-toolbar>
_20
</ion-header>
_20
_20
@if (authenticated) {
_20
<ion-button (click)="login()" data-testid="login-button">Login</ion-button>
_20
} @else {
_20
<ion-button (click)="logout()" data-testid="logout-button">Logout</ion-button>
_20
}
_20
<pre>{{ accessToken }}</pre>
_20
</ion-content>

Display the results in the page.

Add a method to the AuthenticationService that gets the access token:

Modify the Tab1Page to grab the access token.

Display the results in the page.

src/app/core/authentication.service.ts
src/app/tab1/tab1.page.ts
src/app/tab1/tab1.page.html

_87
import { Injectable } from '@angular/core';
_87
import {
_87
Auth0Provider,
_87
AuthConnect,
_87
AuthResult,
_87
ProviderOptions,
_87
} from '@ionic-enterprise/auth';
_87
import { Capacitor } from '@capacitor/core';
_87
import { SessionService } from './session.service';
_87
_87
@Injectable({
_87
providedIn: 'root',
_87
})
_87
export class AuthenticationService {
_87
private authOptions: ProviderOptions;
_87
private provider: Auth0Provider;
_87
_87
constructor(private session: SessionService) {
_87
const isNative = Capacitor.isNativePlatform();
_87
this.provider = new Auth0Provider();
_87
this.authOptions = {
_87
audience: 'https://io.ionic.demo.ac',
_87
clientId: 'yLasZNUGkZ19DGEjTmAITBfGXzqbvd00',
_87
discoveryUrl:
_87
'https://dev-2uspt-sz.us.auth0.com/.well-known/openid-configuration',
_87
logoutUrl: isNative
_87
? 'io.ionic.acdemo://auth-action-complete'
_87
: 'http://localhost:8100/auth-action-complete',
_87
redirectUri: isNative
_87
? 'io.ionic.acdemo://auth-action-complete'
_87
: 'http://localhost:8100/auth-action-complete',
_87
scope: 'openid offline_access email picture profile',
_87
};
_87
}
_87
_87
initialize(): Promise<void> {
_87
const isNative = Capacitor.isNativePlatform();
_87
return AuthConnect.setup({
_87
platform: isNative ? 'capacitor' : 'web',
_87
logLevel: 'DEBUG',
_87
ios: {
_87
webView: 'private',
_87
},
_87
web: {
_87
uiMode: 'popup',
_87
authFlow: 'PKCE',
_87
},
_87
});
_87
}
_87
_87
async getAccessToken(): Promise<string | undefined> {
_87
const res = await this.getAuthResult();
_87
return res?.accessToken;
_87
}
_87
_87
async isAuthenticated(): Promise<boolean> {
_87
const authResult = await this.getAuthResult();
_87
return (
_87
!!authResult && (await AuthConnect.isAccessTokenAvailable(authResult))
_87
);
_87
}
_87
_87
async login(): Promise<void> {
_87
const authResult = await AuthConnect.login(this.provider, this.authOptions);
_87
this.saveAuthResult(authResult);
_87
}
_87
_87
async logout(): Promise<void> {
_87
const authResult = await this.getAuthResult();
_87
if (authResult) {
_87
await AuthConnect.logout(this.provider, authResult);
_87
this.saveAuthResult(null);
_87
}
_87
}
_87
_87
private async getAuthResult(): Promise<AuthResult | null> {
_87
return this.session.getSession();
_87
}
_87
_87
private async saveAuthResult(authResult: AuthResult | null): Promise<void> {
_87
if (authResult) {
_87
await this.session.setSession(authResult);
_87
} else {
_87
await this.session.clear();
_87
}
_87
}
_87
}

We would not normally grab the access token and display it like that. This is just being done to make sure everything is working. Log in and out a few times. You should see a token while logged in but not while logged out.

We will use an HTTP interceptor to attach the access token to outgoing HTTP requests. Use npx ng generate interceptor to generate the code.

Terminal

_10
npx ng generate interceptor core/interceptors/auth --functional
_10
CREATE src/app/core/interceptors/auth.interceptor.spec.ts (476 bytes)
_10
CREATE src/app/core/interceptors/auth.interceptor.ts (149 bytes)

We now need to build up the interceptor and hook it up so it executes with each request.

src/app/core/interceptors/auth.interceptor.ts
src/main.ts

_10
import { HttpInterceptorFn } from '@angular/common/http';
_10
_10
export const authInterceptor: HttpInterceptorFn = (req, next) => {
_10
return next(req);
_10
};

The Angular CLI generated the start of the interceptor for us. This interceptor currently does nothing.

src/app/core/interceptors/auth.interceptor.ts
src/main.ts

_10
import { HttpInterceptorFn } from '@angular/common/http';
_10
import { inject } from '@angular/core';
_10
import { AuthenticationService } from '../authentication.service';
_10
_10
export const authInterceptor: HttpInterceptorFn = (req, next) => {
_10
const authentication = inject(AuthenticationService);
_10
_10
return next(req);
_10
};

Inject the authentication service.

src/app/core/interceptors/auth.interceptor.ts
src/main.ts

_13
import { HttpInterceptorFn, HttpRequest } from '@angular/common/http';
_13
import { inject } from '@angular/core';
_13
import { AuthenticationService } from '../authentication.service';
_13
_13
const requestRequiresToken = (req: HttpRequest<any>): boolean => {
_13
return !/\/public$/.test(req.url);
_13
};
_13
_13
export const authInterceptor: HttpInterceptorFn = (req, next) => {
_13
const authentication = inject(AuthenticationService);
_13
_13
return next(req);
_13
};

Not all requests require a token. For our made up use-case, paths ending in public do not need a token.

src/app/core/interceptors/auth.interceptor.ts
src/main.ts

_20
import { HttpInterceptorFn, HttpRequest } from '@angular/common/http';
_20
import { inject } from '@angular/core';
_20
import { AuthenticationService } from '../authentication.service';
_20
import { from, mergeMap } from 'rxjs';
_20
_20
const requestRequiresToken = (req: HttpRequest<any>): boolean => {
_20
return !/\/public$/.test(req.url);
_20
};
_20
_20
export const authInterceptor: HttpInterceptorFn = (req, next) => {
_20
const authentication = inject(AuthenticationService);
_20
_20
return from(
_20
requestRequiresToken(req)
_20
? authentication.getAccessToken().then((token) => {
_20
null;
_20
})
_20
: Promise.resolve()
_20
).pipe(mergeMap(() => handle(req)));
_20
};

Before passing the request to the next handler in the pipeline, get the access token if it is required for this request.

src/app/core/interceptors/auth.interceptor.ts
src/main.ts

_26
import { HttpInterceptorFn, HttpRequest } from '@angular/common/http';
_26
import { inject } from '@angular/core';
_26
import { AuthenticationService } from '../authentication.service';
_26
import { from, mergeMap } from 'rxjs';
_26
_26
const requestRequiresToken = (req: HttpRequest<any>): boolean => {
_26
return !/\/public$/.test(req.url);
_26
};
_26
_26
export const authInterceptor: HttpInterceptorFn = (req, next) => {
_26
const authentication = inject(AuthenticationService);
_26
_26
return from(
_26
requestRequiresToken(req)
_26
? authentication.getAccessToken().then((token) => {
_26
if (token) {
_26
request = request.clone({
_26
setHeaders: {
_26
Authorization: 'Bearer ' + token,
_26
},
_26
});
_26
}
_26
})
_26
: Promise.resolve()
_26
).pipe(mergeMap(() => handle(req)));
_26
};

If the token exists add it to the request as a bearer token.

src/app/core/interceptors/auth.interceptor.ts
src/main.ts

_20
import { enableProdMode } from '@angular/core';
_20
import { bootstrapApplication } from '@angular/platform-browser';
_20
import { RouteReuseStrategy, provideRouter } from '@angular/router';
_20
import { IonicRouteStrategy, provideIonicAngular } from '@ionic/angular/standalone';
_20
_20
import { routes } from './app/app.routes';
_20
import { AppComponent } from './app/app.component';
_20
import { environment } from './environments/environment';
_20
_20
if (environment.production) {
_20
enableProdMode();
_20
}
_20
_20
bootstrapApplication(AppComponent, {
_20
providers: [
_20
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
_20
provideIonicAngular(),
_20
provideRouter(routes),
_20
],
_20
});

The main.ts file needs to be updated to provide the interceptor.

src/app/core/interceptors/auth.interceptor.ts
src/main.ts

_23
import { enableProdMode } from '@angular/core';
_23
import { bootstrapApplication } from '@angular/platform-browser';
_23
import { RouteReuseStrategy, provideRouter } from '@angular/router';
_23
import { IonicRouteStrategy, provideIonicAngular } from '@ionic/angular/standalone';
_23
_23
import { routes } from './app/app.routes';
_23
import { AppComponent } from './app/app.component';
_23
import { environment } from './environments/environment';
_23
import { provideHttpClient, withInterceptors } from '@angular/common/http';
_23
import { authInterceptor } from '@app/core';
_23
_23
if (environment.production) {
_23
enableProdMode();
_23
}
_23
_23
bootstrapApplication(AppComponent, {
_23
providers: [
_23
{ provide: RouteReuseStrategy, useClass: IonicRouteStrategy },
_23
provideHttpClient(withInterceptors([authInterceptor])),
_23
provideIonicAngular(),
_23
provideRouter(routes),
_23
],
_23
});

This interceptors are typically provided with the HTTP client.

The Angular CLI generated the start of the interceptor for us. This interceptor currently does nothing.

Inject the authentication service.

Not all requests require a token. For our made up use-case, paths ending in public do not need a token.

Before passing the request to the next handler in the pipeline, get the access token if it is required for this request.

If the token exists add it to the request as a bearer token.

The main.ts file needs to be updated to provide the interceptor.

This interceptors are typically provided with the HTTP client.

src/app/core/interceptors/auth.interceptor.ts
src/main.ts

_10
import { HttpInterceptorFn } from '@angular/common/http';
_10
_10
export const authInterceptor: HttpInterceptorFn = (req, next) => {
_10
return next(req);
_10
};

Handle 401 Errors

Now that the access token is sent to the backend, we need to also handle the case where the backend rejects the access token resulting in a 401 - Unauthorized response status. This can be done through another HTTP interceptor.

Terminal

_10
npx ng generate interceptor core/interceptors/unauth --functional
_10
CREATE src/app/core/interceptors/unauth.interceptor.spec.ts (484 bytes)
_10
CREATE src/app/core/interceptors/unauth.interceptor.ts (151 bytes)

The interceptor needs to be built out to clear the session data and navigate to the login page when a 401 error occurs.

src/app/core/interceptors/unauth.interceptor.ts
src/main.ts

_10
import { HttpInterceptorFn } from '@angular/common/http';
_10
_10
export const unauthInterceptor: HttpInterceptorFn = (req, next) => {
_10
return next(req);
_10
};

We are starting with the generated interceptor.

src/app/core/interceptors/unauth.interceptor.ts
src/main.ts

_10
import { HttpInterceptorFn } from '@angular/common/http';
_10
import { tap } from 'rxjs';
_10
_10
export const unauthInterceptor: HttpInterceptorFn = (req, next) => {
_10
return next(req).pipe(
_10
tap({
_10
error: async (err: unknown) => {},
_10
})
_10
);
_10
};

Tap into the observable pipeline for the request. Notice that we only need to handle the error case.

src/app/core/interceptors/unauth.interceptor.ts
src/main.ts

_16
import { HttpInterceptorFn } from '@angular/common/http';
_16
import { inject } from '@angular/core';
_16
import { tap } from 'rxjs';
_16
import { NavController } from '@ionic/angular';
_16
import { SessionService } from '../session.service';
_16
_16
export const unauthInterceptor: HttpInterceptorFn = (req, next) => {
_16
const navigation = inject(NavController);
_16
const sesion = inject(SessionService);
_16
_16
return next(req).pipe(
_16
tap({
_16
error: async (err: unknown) => {},
_16
})
_16
);
_16
};

Inject the NavController and SessionService.

src/app/core/interceptors/unauth.interceptor.ts
src/main.ts

_21
import { HttpErrorResponse, HttpInterceptorFn } from '@angular/common/http';
_21
import { inject } from '@angular/core';
_21
import { tap } from 'rxjs';
_21
import { NavController } from '@ionic/angular';
_21
import { SessionService } from '../session.service';
_21
_21
export const unauthInterceptor: HttpInterceptorFn = (req, next) => {
_21
const navigation = inject(NavController);
_21
const sesion = inject(SessionService);
_21
_21
return next(req).pipe(
_21
tap({
_21
error: async (err: unknown) => {
_21
if (err instanceof HttpErrorResponse && err.status === 401) {
_21
await session.clear();
_21
navigation.navigateRoot(['/', 'tabs', 'tab1']);
_21
}
_21
},
_21
})
_21
);
_21
};

If the error is an HttpErrorResponse and the status is 401, clear the session and redirect so the user can log in again.

We are starting with the generated interceptor.

Tap into the observable pipeline for the request. Notice that we only need to handle the error case.

Inject the NavController and SessionService.

If the error is an HttpErrorResponse and the status is 401, clear the session and redirect so the user can log in again.

src/app/core/interceptors/unauth.interceptor.ts
src/main.ts

_10
import { HttpInterceptorFn } from '@angular/common/http';
_10
_10
export const unauthInterceptor: HttpInterceptorFn = (req, next) => {
_10
return next(req);
_10
};

Next Steps

Currently, if we have an AuthResult with an access token we assume the user is properly authenticated. If you would like to expand this logic to first make sure the access token has not expired, and try to refresh it if it has, then please have a look at the tutorial on refreshing the session.

Happy coding!! 🤓