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.
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.
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.
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.
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.
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:
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:
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
<divclass="content"role="main"><h2class="title">List Of Posts</h2><divid="data"><liclass="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><arouterLink="/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:
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.
An Example of a Posts 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:
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]})exportclassAppModule{}
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.
constroutes: Routes=[{path:'post/:id',component: PostComponent},{path:'posts',component: PostsComponent},{path:'register',component: RegisterComponent},//<----- New line added.
];@NgModule({imports:[RouterModule.forRoot(routes)],exports:[RouterModule]})exportclassAppRoutingModule{}
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:
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:
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.
constroutes: 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]})exportclassAppRoutingModule{}
Next, we’ll set up the app to verify user credentials. Replace the contents of the src/app/login/login.component.ts file with:
Now, replace the contents of the src/app/login/login.component.html file with:
1
2
3
4
5
6
7
8
9
<divclass="log-form"><h2>Login to your account</h2><div[innerHTML]="error_message"></div><form><inputtype="text"name="username"[(ngModel)]="username"placeholder="Username"required/><inputtype="password"name="password"[(ngModel)]="password"placeholder="Password"required/><buttontype="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:
An Example of a Registration Page
An Example of a Login Page
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:
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';exportconstauthGuard: CanActivateFn=(route,state)=>{// Use dependency injection to get an instance of the AuthService.
constauthService=inject(AuthService);// Return whether the user is logged in using the AuthService.
returnauthService.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:
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.
constroutes: 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]})exportclassAppRoutingModule{}
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:
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']})exportclassPostComponent{post={title:'',content:'',};constructor(privateroute: ActivatedRoute,privatehttp: HttpClient){}asyncsend_graphql_request(query: string){letheaders={};// New code begins here.
consttoken=localStorage.getItem('auth_token');if(token!==null){constparsedToken=JSON.parse(token);if(parsedToken){headers={'Authorization':'Bearer '+parsedToken};}}// New code ends here.
constresponse=awaitthis.http.post<any>(HERE_GOES_YOUR_GRAPHQL_API_ENDPOINT,{query: query},{headers}).toPromise()returnresponse;}ngOnInit(){constpost_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(typeofresponse.errors=='undefined'&&typeofresponse.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 toBranko Radulovicfrom the Toptal Engineering Blog’s editorial team for reviewing the code samples and technical content presented in this article.