Using GraphQL for JWT Authentication in Angular with WordPress

In contemporary software development, decoupling, the practice of dividing an application into separate parts, has become the norm. Both businesses and developers prefer this approach because it creates a distinct boundary between an application’s presentation layer (front end) and its data access layer (back end). This separation improves efficiency by enabling simultaneous development by multiple teams and provides the flexibility to select the best technologies for each part.

Due to its modular structure, a decoupled system allows for individual components to be scaled, modified, or even completely replaced as needed, adapting to the evolving requirements of the system. This practice is widespread across various digital platforms, including areas such as e-commerce, online banking, community forums, and social media.

Despite the numerous advantages of a decoupled system, it’s not without potential drawbacks. Since communication happens across separate modules or services, it can lead to latency, potentially slowing down system performance. Additionally, traditional browser cookie and server-side authentication methods designed for monolithic applications become more complex to implement.

To overcome these challenges, developers can utilize protocols like GraphQL, REST, and gRPC. These protocols facilitate efficient communication between components, mitigate delays, and provide a framework for implementing authentication. This tutorial demonstrates how decoupled apps can excel: We will use a WordPress-powered Angular app to establish secure communication using GraphQL and JWT, a widely used token-based authentication method.

Building a Responsive and Secure Decoupled App: An Angular-WordPress Case Study

We will create a blog application with a headless WordPress back end and an Angular front end. WordPress, a popular and powerful content management system (CMS), is well-suited for managing and serving blog content. Angular is a strategic choice for the front end because it enables dynamic content updates without full-page reloads, resulting in faster user interactions. Communication between these two layers will be handled by GraphQL.

Architecture with annotations of a simple, unprotected, decoupled blog app

At first, our app will be designed to retrieve blog post content and display the post titles to users in a list format. Once operational, we will enhance the unprotected blog application by integrating JWT-based authentication. This token-based authentication ensures that only logged-in users have access to the full content. Unauthenticated visitors will still see the list of post titles but will be prompted to sign in or register to read a full post.

Architecture with annotations of an enhanced, decoupled blog app

On the front end, a route guard will verify user permissions to determine if a route can be accessed, while the HTTP module will handle HTTP communication. On the back end, GraphQL will serve as the communication medium for the app, implemented as an API interface over HTTP.

Please note: This tutorial focuses on the integration of separate front-end and back-end systems using an efficient cross-domain solution, specifically utilizing GraphQL for authentication in an Angular-WordPress application. It does not delve into the broader and more complex topic of cybersecurity. Moreover, this tutorial does not guarantee the restriction of GraphQL access exclusively to logged-in users. Achieving that would require configuring GraphQL to recognize access tokens, which is outside the scope of this demonstration.

Step 1: Configuring the Application Environment

This is where our project begins:

  1. Use a new or existing installation of WordPress on your computer.
  2. Log in to your WordPress dashboard as an administrator. Navigate to Settings/General from the menu. In the membership section, enable the Anyone can register option.
  3. Install the WPGraphQL plugin. You can download it from the WordPress plugin directory and then activate it.
  4. To extend the functionality of the WPGraphQL plugin, we will utilize the WPGraphQL JWT Authentication plugin. As it’s not available in the WordPress plugin directory, install it according to its instructions, making sure to define a secret key as instructed in the readme.md file. The plugin won’t function without it.
  5. Install a fresh install of Angular on your local machine. Then, create a new Angular workspace and application with routing and CSS support by running the following command: ng n my-graphql-wp-app --routing --style css.
    • Note: This tutorial was written using Angular version 16. If you’re using a later version, you may need to adjust the steps and/or file names accordingly.

With your WordPress setup complete, the back end of your basic blog site is ready.

Step 2: Developing the App’s Front End

Before establishing communication between the two sides of our application, we need to have all the necessary components in place. In this step, we’ll set up the essential elements: create pages, add and configure routes, and integrate the HTTP module. With these pieces in position, we can begin fetching and displaying content.

The WPGraphQL plugin, which we activated earlier, enables WordPress to expose data through our app’s GraphQL API. By default, the GraphQL endpoint is located at YOUR-SITE-URL/graphql, where YOUR-SITE-URL represents the URL of your WordPress installation. For instance, if your site URL is example.com, your app’s GraphQL API endpoint would be example.com/graphql.

Creating the App’s Pages

Our simple app will initially consist of only two pages: posts (displaying a list of all post titles) and post (showing the content of a single post).

We can generate these content pages using the Angular CLI. Open your preferred terminal application, navigate to the Angular project’s root directory, and run the following commands:

1
ng generate component posts && ng generate component post

However, these new pages won’t be accessible without a rendering container and properly configured routes.

Adding Routes

A route allows users to directly access a specific page via a corresponding URL or navigation link. While a fresh Angular installation includes routing capabilities, this feature is not enabled by default.

To add routes to our app, replace the contents of the src/app/app-routing.module.ts file with the following code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { PostComponent } from './post/post.component';
import { PostsComponent } from './posts/posts.component';

const routes: Routes = [
  { path: 'post/:id', component: PostComponent },
  { path: 'posts', component: PostsComponent },
];

@NgModule( {
  imports: [ RouterModule.forRoot( routes ) ],
  exports: [ RouterModule ]
} )

export class AppRoutingModule { }

With this code, we’ve added two routes to our application: one for the posts page and another for the post page.

Adding the Router Outlet Component

To utilize routing, we need the router-outlet directive. This directive enables Angular to dynamically render the appropriate content for the current route as the user navigates through the app.

Open the src/app/app.component.html file in your preferred code editor and replace its contents with the following:

1
<router-outlet></router-outlet>

Our route setup is now complete. But before we can fetch any content, we need to set up the HTTP module middleware.

Integrating the HTTP Module

For a page to fetch content and display it to users, it needs to be able to send HTTP requests to the back end. Replace the contents of the src/app/app.module.ts file with the following code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http'; 
import { AppRoutingModule } from './app-routing.module';
import { PostComponent } from './post/post.component';
import { PostsComponent } from './posts/posts.component';
import { AppComponent } from './app.component';

@NgModule( {
  declarations: [
    AppComponent,
    PostComponent,
    PostsComponent,
  ],
  imports: [
    BrowserModule,
    HttpClientModule, 
    AppRoutingModule
  ],
  providers: [],
  bootstrap: [ AppComponent ]
} )

export class AppModule { }

This code integrates Angular’s built-in HTTP module, allowing us to send HTTP requests to fetch data from our WordPress back end.

Setting Up Content Fetching and Display

Now let’s configure our app to fetch and display content on the blog pages.

The Posts Page

Replace the contents of the src/app/posts/posts.component.ts file with the following code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component( {
  selector: 'app-posts',
  templateUrl: './posts.component.html',
  styleUrls: ['./posts.component.css']
} )

export class PostsComponent
{
  posts = [];

  constructor( private http: HttpClient ) { }

  async send_graphql_request( query: string )
  {    
    const response = await this.http.post<any>( HERE_GOES_YOUR_GRAPHQL_API_ENDPOINT, { query: query }, { } ).toPromise()

    return response;
  }

  ngOnInit()
  {
    this.send_graphql_request(
      `query GetPostsQuery {
        posts(where: {orderby: {field: DATE, order: DESC}}) {
          nodes {
            databaseId
            featuredImage {
              node {
                sourceUrl
              }
            }
            title
            excerpt
          }
        }
      }`
    )
    .then( response =>
    {
      if( typeof response.errors == 'undefined' && typeof response.data !== 'undefined' )
      {
        this.posts = response.data.posts.nodes;
      }
      else
      {
        console.log( 'Something went wrong! Please try again.' );
      }
    } )
  }
}

When a user visits the posts page, this code is executed and sends an HTTP request to the back end. This request utilizes a GraphQL schema to fetch the most recent posts from the WordPress database.

Next, to display the retrieved posts, replace the contents of the src/app/posts/posts.component.html file with the following code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<div class="content" role="main">
  <h2 class="title">List Of Posts</h2>
  <div id="data">
    <li class="post" *ngFor="let post of posts">
      <img *ngIf="post['featuredImage']" src="{{post['featuredImage']['node']['sourceUrl']}}">
      <img *ngIf="!post['featuredImage']" src="https://picsum.photos/300/200">
      <h3>{{post['title']}}</h3>
        <a routerLink="/post/{{post['databaseId']}}">View Post</a>
    </li>
  </div>
</div>

To style the posts page, add the following CSS rules to the app/src/posts/posts.component.css file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.content {
  width: 900px;
  margin: 0 auto;
}
h2.title {
  text-align: center;
}
li.post {
  list-style: none;
  text-align: center;
  flex: 0 0 28.333333%;
  margin-bottom: 15px;
}
img {
  max-width: 100%;
}
div#data {
  display: flex;
  flex-direction: row;
  justify-content: center;
  gap: 5%;
  flex-wrap: wrap;
}

This will give the posts page a clean and minimalist appearance.

The Post Page

We’ll follow a similar process for the post page. Replace the contents of the src/app/post/post.component.ts file with the following code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { ActivatedRoute } from '@angular/router';

@Component( {
  selector: 'app-post',
  templateUrl: './post.component.html',
  styleUrls: ['./post.component.css']
} )

export class PostComponent
{
  post = {
    title : '',
    content : '',
  };

  constructor( private route: ActivatedRoute, private http: HttpClient ) { }

  async send_graphql_request( query: string )
  { 
    const response = await this.http.post<any>( HERE_GOES_YOUR_GRAPHQL_API_ENDPOINT, { query: query }, {} ).toPromise()

    return response;
  }

  ngOnInit()
  {
    const post_id = this.route.snapshot.paramMap.get( 'id' );
    
    this.send_graphql_request(
      `query GetPostsQuery {
        post(id: "${post_id}", idType: DATABASE_ID) {
          content
          title
        }
      }`
    )
    .then( response =>
    {
      if( typeof response.errors == 'undefined' && typeof response.data !== 'undefined' )
      {
        this.post = response.data.post;
      }
      else
      {
        console.log( 'Something went wrong! Please try again.' );
      }
    } )
  }
}

To display the content fetched from the back end, replace the contents of the src/app/post/post.component.html file with:

1
2
3
4
<div class="content" role="main">
  <h2 class="title">{{post.title}}</h2>
  <div [innerHTML]="post.content"></div>
</div>

Finally, add the following CSS rules to the app/src/post/post.component.css file:

1
2
3
4
5
6
7
.content {
  width: 900px;
  margin: 0 auto;
}
h2.title {
  text-align: center;
}

These styles will give the post page a consistent look and feel with the posts page.

Progress Check

At this point, we have set up the essential elements of our app and established the core infrastructure for communication between the Angular front end and the headless WordPress back end. Open your browser and verify that the app’s sample content is viewable.

The posts page.
An Example of a Posts Page
The post page.
An Example of a Post Page

Step 3: Implementing Authentication

By adding authentication, we can restrict access to the post page to authorized users only. To achieve this, we will add two new pages to our app: a register page and a login page.

The Registration Page

Creating the Page

Return to your terminal application, navigate to the Angular project’s root directory, and execute the following command:

1
ng generate component register

This command will create a new component named register.

To enable the use of HTML form input fields as Angular inputs, we need to import Angular’s FormsModule into the src/app/app.module.ts file. Replace the existing contents of this file with the following code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module';
import { PostComponent } from './post/post.component';
import { PostsComponent } from './posts/posts.component';
import { AppComponent } from './app.component';
import { RegisterComponent } from './register/register.component';
import { FormsModule } from '@angular/forms'; //<----- New line added.

@NgModule( {
  declarations: [
    AppComponent,
    PostComponent,
    PostsComponent,
    RegisterComponent,
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    AppRoutingModule,
    FormsModule //<----- New line added.
  ],
  providers: [],
  bootstrap: [ AppComponent ]
} )

export class AppModule { }

In-line comments have been added to highlight the specific changes made.

Adding a Route

Now let’s create a route for the register page. Replace the contents of the src/app/app-routing.module.ts file with:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { PostComponent } from './post/post.component';
import { PostsComponent } from './posts/posts.component';
import { RegisterComponent } from './register/register.component'; //<----- New line added.

const routes: Routes = [
  { path: 'post/:id', component: PostComponent },
  { path: 'posts', component: PostsComponent },
  { path: 'register', component: RegisterComponent }, //<----- New line added.
];

@NgModule( {
  imports: [ RouterModule.forRoot( routes ) ],
  exports: [RouterModule]
} )

export class AppRoutingModule { }

With the route in place, we can configure the app to verify new user credentials and finalize their registration. Replace the contents of the src/app/register/register.component.ts file with:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { HttpClient } from '@angular/common/http';

@Component( {
  selector: 'app-register',
  templateUrl: './register.component.html',
  styleUrls: ['./register.component.css']
} )

export class RegisterComponent
{
  constructor( public router: Router, private http: HttpClient ) {}
  
  username = '';
  
  email = '';
  
  password = '';
  
  error_message = '';

  async send_graphql_request( query: string )
  {    
    const response = await this.http.post<any>( HERE_GOES_YOUR_GRAPHQL_API_ENDPOINT, { query: query }, { } ).toPromise()

    return response;
  }
  
  register()
  {
    document.getElementsByTagName( 'button' )[0].setAttribute( 'disabled', 'disabled' );
    
    document.getElementsByTagName( 'button' )[0].innerHTML = 'Loading';

    this.send_graphql_request(
      `mutation RegisterMutation {
        registerUser(input: {username: "${this.username}", email: "${this.email}", password: "${this.password}"}) {
          user {
            databaseId
          }
        }
      }`
    )
    .then( response =>
    {        
        if( typeof response.errors == 'undefined' && typeof response.data.registerUser.user.databaseId !== 'undefined' )
        {
          this.router.navigate( ['/login'] );
        }
        else
        {
          this.error_message = this.decodeHTMLEntities( response.errors[0].message );
        }

	  document.getElementsByTagName( 'button' )[0].innerHTML = 'Register';
       document.getElementsByTagName( 'button' )[0].removeAttribute( 'disabled' );
    } )
  }

  decodeHTMLEntities( text : string )
  {
    const entities = [
      ['amp', '&'],
      ['apos', '\''],
      ['#x27', '\''],
      ['#x2F', '/'],
      ['#39', '\''],
      ['#47', '/'],
      ['lt', '<'],
      ['gt', '>'],
      ['nbsp', ' '],
      ['quot', '"']
    ];

    for ( let i = 0, max = entities.length; i < max; ++i )
      text = text.replace( new RegExp( '&' + entities[i][0] + ';', 'g'), entities[i][1] );
    
    return text;
  }
}

The register() method in this code snippet sends the new user’s credentials to our app’s GraphQL API for verification. If the registration is successful, a new user is created, and the API returns a JSON response containing the newly created user ID. If not, an error message is displayed, guiding the user accordingly.

Adding Content

To add a user registration form to the page, replace the contents of the src/app/register/register.component.html file with the following code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<div class="register-form">
  <h2>Register</h2>
  <div [innerHTML]="error_message"></div>
  <form>
    <input type="text" name="username" [(ngModel)]="username" placeholder="Username" required />
    <input type="text" name="email" [(ngModel)]="email" placeholder="Email" required />
    <input type="password" name="password" [(ngModel)]="password" placeholder="Password" required />
    <button type="submit" class="btn" (click)="register()">Register</button>
  </form>
</div>

Now let’s repeat these steps for the login page.

The Login Page

Creating the Page

In your terminal, navigate back to the Angular project’s root directory and run:

1
ng generate component login

Create the login route by replacing the contents of the src/app/app-routing.module.ts file with:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { PostComponent } from './post/post.component';
import { PostsComponent } from './posts/posts.component';
import { RegisterComponent } from './register/register.component';
import { LoginComponent } from './login/login.component'; //<----- New line added.

const routes: Routes = [
  { path: 'post/:id', component: PostComponent },
  { path: 'posts', component: PostsComponent },
  { path: 'register', component: RegisterComponent },
  { path: 'login', component: LoginComponent }, //<----- New line added.
];

@NgModule( {
  imports: [ RouterModule.forRoot( routes ) ],
  exports: [RouterModule]
} )

export class AppRoutingModule { }

Next, we’ll set up the app to verify user credentials. Replace the contents of the src/app/login/login.component.ts file with:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { HttpClient } from '@angular/common/http';

@Component( {
  selector: 'app-login',
  templateUrl: './login.component.html',
  styleUrls: ['./login.component.css']
} )

export class LoginComponent
{
  constructor( public router: Router, private http: HttpClient ) {}
  
  username = '';
  
  password = '';

  error_message= '';

  async send_graphql_request( query: string )
  {    
    const response = await this.http.post<any>( HERE_GOES_YOUR_GRAPHQL_API_ENDPOINT, { query: query }, { } ).toPromise()

    return response;
  }
  
  login()
  {
    document.getElementsByTagName( 'button' )[0].setAttribute( 'disabled', 'disabled' );
    
    document.getElementsByTagName( 'button' )[0].innerHTML = 'Loading';

    this.send_graphql_request(
      `mutation LoginMutation {
        login(input: {username: "${this.username}", password: "${this.password}"}) {
          authToken
        }
      }`
    )
    .then( response =>
    {        
        if( typeof response.errors == 'undefined' && typeof response.data.login.authToken !== 'undefined' )
        {
          localStorage.setItem( 'auth_token', JSON.stringify( response.data.login.authToken ) );

          this.router.navigate( ['/posts'] );
        }
        else
        {
          this.error_message = this.decodeHTMLEntities( response.errors[0].message );
        }

	   document.getElementsByTagName( 'button' )[0].innerHTML = 'Login';

        document.getElementsByTagName( 'button' )[0].removeAttribute( 'disabled' );
    } )
  }

  decodeHTMLEntities( text : string )
  {
    var entities = [
      ['amp', '&'],
      ['apos', '\''],
      ['#x27', '\''],
      ['#x2F', '/'],
      ['#39', '\''],
      ['#47', '/'],
      ['lt', '<'],
      ['gt', '>'],
      ['nbsp', ' '],
      ['quot', '"']
    ];

    for ( var i = 0, max = entities.length; i < max; ++i )
      text = text.replace( new RegExp( '&' + entities[i][0] + ';', 'g'), entities[i][1] );
    
    return text;
  }
}

Now, replace the contents of the src/app/login/login.component.html file with:

1
2
3
4
5
6
7
8
9
<div class="log-form">
  <h2>Login to your account</h2>
  <div [innerHTML]="error_message"></div>
  <form>
    <input type="text" name="username" [(ngModel)]="username" placeholder="Username" required />
    <input type="password" name="password" [(ngModel)]="password" placeholder="Password" required />
    <button type="submit" class="btn" (click)="login()">Login</button>
  </form>
</div>

This code adds a login form to the page with input fields for user credentials. Similar to the registration page, this code sends the existing user’s credentials to the app’s GraphQL API for validation. If the credentials are correct, the API returns a JWT, which is then stored in the browser’s localStorage for later use. If the credentials are invalid or the JWT has expired, an error message guides the user.

Progress Check

To test the authentication functionality, try registering as a new user and then logging in to the app. To log out, you can simply remove the JWT from your browser’s localStorage. Your results should resemble the following screenshots:

The registration page.
An Example of a Registration Page
The login page.
An Example of a Login Page
The login page with an error message.
An Example of a Login Page With Incorrect Credentials

Step 4: Implementing Access Restrictions

Now that we have authentication in place, our next step is to restrict access to the post route, ensuring that only logged-in users can view it.

Creating and Setting Up the Guard and Service

In your terminal, return to the root directory of your Angular project and run the following command:

1
ng generate service auth && ng generate guard auth

You will be presented with a list of interfaces to implement. Select CanActivate to create a guard that will verify the user’s authentication status using a service, which we will also create in this step.

Now let’s set up our guard and service to manage the authentication process. Replace the contents of the src/app/auth.service.ts file with:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';

@Injectable( {
  providedIn: 'root'
} )

export class AuthService
{
  router : any;
  
  constructor( private route: Router )
  {
    this.router = route
  }

  loggedIn()
  {
    if( localStorage.getItem( 'auth_token' ) != null ) return true;

    this.router.navigate( ['/login'] ); return false;
  }
}

This code sets up the service responsible for managing user authentication. If a JWT is present, the service returns a positive response to the guard, indicating that the user is logged in. Otherwise, it returns false, signaling that the user is not authenticated.

To restrict the post route based on the information received from the authentication service, replace the contents of the src/app/auth.guard.ts file with:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import { CanActivateFn } from '@angular/router';
import { AuthService } from './auth.service';
import { inject } from '@angular/core';

export const authGuard: CanActivateFn = ( route, state ) =>
{
  // Use dependency injection to get an instance of the AuthService.
  const authService = inject( AuthService );

  // Return whether the user is logged in using the AuthService.
  return authService.loggedIn();
};

With this code in place, the post page is now restricted and only accessible to authenticated users.

Restricting the Post Page’s Route

To enforce the restriction on the post page’s route, we need to implement a route-specific guard. Replace the contents of the src/app/app-routing.module.ts file with:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import { NgModule } from '@angular/core';
import { RouterModule, Routes, CanActivate } from '@angular/router';
import { PostComponent } from './post/post.component';
import { PostsComponent } from './posts/posts.component';
import { LoginComponent } from './login/login.component';
import { RegisterComponent } from './register/register.component';
import { authGuard } from './auth.guard'; //<----- New line added.

const routes: Routes = [
  { path: 'post/:id', component: PostComponent, canActivate: [ authGuard ] }, //<----- New code added.
  { path: 'posts', component: PostsComponent },
  { path: 'register', component: RegisterComponent },
  { path: 'login', component: LoginComponent },
];

@NgModule( {
  imports: [ RouterModule.forRoot( routes ) ],
  exports: [ RouterModule ]
} )

export class AppRoutingModule { }

With this modification, the post page’s route now utilizes Angular’s canActivate method to ensure that only authenticated users can access it.

Verifying the JWT

Finally, we are ready to validate the JWT stored in the user’s browser. This validation ensures that the JWT is not expired and remains valid in real-time. Replace the contents of the src/app/post/post.component.ts file with:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { ActivatedRoute } from '@angular/router';

@Component( {
  selector: 'app-post',
  templateUrl: './post.component.html',
  styleUrls: ['./post.component.css']
} )

export class PostComponent
{
  post = {
    title : '',
    content : '',
  };

  constructor( private route: ActivatedRoute, private http: HttpClient ) { }

  async send_graphql_request( query: string )
  {
    
    let headers = {};
    
    // New code begins here.
    const token = localStorage.getItem( 'auth_token' );
    
    if( token !== null )
    {
      const parsedToken = JSON.parse( token );

      if( parsedToken )
      {
        headers = { 'Authorization': 'Bearer ' + parsedToken };
      }
    }
    // New code ends here.
    
    const response = await this.http.post<any>( HERE_GOES_YOUR_GRAPHQL_API_ENDPOINT, { query: query }, { headers } ).toPromise()

    return response;
  }

  ngOnInit()
  {
    const post_id = this.route.snapshot.paramMap.get( 'id' );
    
    this.send_graphql_request(
      `query GetPostsQuery {
        post(id: "${post_id}", idType: DATABASE_ID) {
          content
          title
        }
      }`
    )
    .then( response =>
    {
      if( typeof response.errors == 'undefined' && typeof response.data !== 'undefined' )
      {
        this.post = response.data.post;
      }
      else
      {
        console.log( 'Something went wrong! Please try again.' );
      }
    } )
  }
}

This code injects the stored JWT as an bearer authorization header into every HTTP request made by the user while on the post page. For clarity, the new code additions are highlighted with comments.

Final Output: Delivering a Dynamic and Secure User Experience

To confirm that our access restrictions are working correctly, make sure you are logged out of the app and try accessing the posts page. Then, attempt to access the post page. You should be redirected to the login page. Log in to view the fetched content on the post page. If everything functions as expected, congratulations! You have successfully completed this tutorial and developed a decoupled and secure single-page application.

In today’s digital landscape, providing a dynamic and secure user experience is no longer optional; it’s an essential requirement. The concepts and techniques covered in this tutorial can be applied to your future decoupled projects to achieve scalability and provide developers with the flexibility to design and deliver impactful websites.


Special thanks to Branko Radulovic from the Toptal Engineering Blog’s editorial team for reviewing the code samples and technical content presented in this article.

Licensed under CC BY-NC-SA 4.0