If you want to use the WordPress API and connect your Ionic app to it, most likely you also want to make user of the users your page already has (or use it as a simple backend for your next app).
But instead of using the basic authentication from WordPress, a more elegant way is to use JWT authentication, which is possible with the help of a simple plugin.
In this tutorial we will prepare a WordPress instance for JWT and also build an Ionic app to register new users, and to also sign them in and make authorised requests to the WordPress API using an interceptor.
There’s also a full blown course on using Ionic with WordPress that even covers push notifications with OneSignal, so if you really want to get into this topic, check out the Ionic Academy and the course library!
WordPress Preparation
Before we get started with our app we need to prepare WordPress. First of all, we can install 2 plugins:
These plugins will help us to set up JWT authentication for the WordPress API, and also allow registeration of new users directly through the API.
Now we also need some small changes, the first one could be added to your wp-content/themes/{yourthemename}/functions.php
function add_cors_http_header(){ header("Access-Control-Allow-Origin: *"); } add_action('init','add_cors_http_header'); add_filter('kses_allowed_protocols', function($protocols) { $protocols[] = 'capacitor'; return $protocols; }); add_filter('kses_allowed_protocols', function($protocols) { $protocols[] = 'ionic'; return $protocols; });
This fix enables CORS and also allows different protocols – and issue a lot of you encountered with my last Ionic + WordPress tutorial on real devices!
In order to set up the JWT part, we also need to add these 2 lines to the wp-config.php file:
define('JWT_AUTH_CORS_ENABLE', true); define('JWT_AUTH_SECRET_KEY', 'your-top-secret-key');
Generate a key or use a dummy string while testing, and then move on to the last changes.
As described in the setup guide of the JWT plugin, we also need to add the highlighted lines to our .htaccess like this:
<IfModule mod_rewrite.c> RewriteEngine On RewriteBase /wordpress/ RewriteRule ^index\.php$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /wordpress/index.php [L] RewriteCond %{HTTP:Authorization} ^(.*) RewriteRule ^(.*) - [E=HTTP_AUTHORIZATION:%1] </IfModule>
Now the WordPress instance is ready, and hopefully everything still works with your page!
Ionic App Setup
Let’s kick off the Ionic app with a blank template, one additional service for all of our API interaction and also install Ionic storage to store our JWT later down the road:
ionic start devdacticWP blank --type=angular cd ./devdacticWP ionic g service services/api npm i @ionic/storage
To make our WordPress URL easily accessible, we can set it directly inside our environments/environment.ts:
export const environment = { production: false, apiUrl: 'http://192.168.2.123:8888/wordpress/wp-json' };
I’ve used a local MAMP testing instance and the blog was running in a folder “wordpress” – change your URL according to your settings!
Note: I recommend adding your local IP (or public website if possible), localhost won’t work once you deploy your app to a device!
Now we just need to import all relevant modules to our main module, and don’t worry the error about the interceptor, we’ll get to that file soon.
Go ahead and change your app.module.ts to:
import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { RouteReuseStrategy } from '@angular/router'; import { IonicModule, IonicRouteStrategy } from '@ionic/angular'; import { SplashScreen } from '@ionic-native/splash-screen/ngx'; import { StatusBar } from '@ionic-native/status-bar/ngx'; import { AppComponent } from './app.component'; import { AppRoutingModule } from './app-routing.module'; import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { IonicStorageModule } from '@ionic/storage'; import { JwtInterceptor } from './interceptors/jwt.interceptor'; @NgModule({ declarations: [AppComponent], entryComponents: [], imports: [ BrowserModule, IonicModule.forRoot(), AppRoutingModule, HttpClientModule, IonicStorageModule.forRoot() ], providers: [ StatusBar, SplashScreen, { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }, { provide: HTTP_INTERCEPTORS, useClass: JwtInterceptor, multi: true } ], bootstrap: [AppComponent] }) export class AppModule {}
The interceptor will be used whenever we make a HTTP request later, but for now let’s get a token first of all.
Service and Interceptor
In our service we will handle all API interaction, and also create a BehaviorSubject
which represents the current state of the user. You might have used Events in the past, but since Ionic 5 you should move to RxJS instead.
Let’s go through all of our service functions quickly:
- constructor: Load any previously stored JWT and emit the data on our Subject
- signIn: Call the API route to sign in a user and retrieve a JWT. This token will be written to our Storage, and once this operation is finished we emit the new value on our Subject
- signUp: Call the API route to create a new users, which is added through the plugin we installed
- resetPassword: Call the API route to send out a reset password email, which is added through the plugin we installed
- getPrivatePosts: Get a list of private posts from the WordPress blog – the user needs to be authenticated for this and have at least the role Editor!
- getCurrentUser / getUserValue: Helper functions to get an Observable or the current value from our Subject
- logout: Remove any stored token from our storage
Not a lot of logic, basically we make simply use of the WordPress API, so go ahead and add the following to your services/api.service.ts:
import { Injectable } from '@angular/core'; import { BehaviorSubject, from } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { Platform } from '@ionic/angular'; import { environment } from '../../environments/environment'; import { map, switchMap, tap } from 'rxjs/operators'; import { Storage } from '@ionic/storage'; const JWT_KEY = 'myjwtstoragekey'; @Injectable({ providedIn: 'root' }) export class ApiService { private user = new BehaviorSubject(null); constructor(private http: HttpClient, private storage: Storage, private plt: Platform) { this.plt.ready().then(() => { this.storage.get(JWT_KEY).then(data => { if (data) { this.user.next(data); } }) }) } signIn(username, password) { return this.http.post(`${environment.apiUrl}/jwt-auth/v1/token`, { username, password }).pipe( switchMap(data => { return from(this.storage.set(JWT_KEY, data)); }), tap(data => { this.user.next(data); }) ); } signUp(username, email, password) { return this.http.post(`${environment.apiUrl}/wp/v2/users/register`, { username, email, password }); } resetPassword(usernameOrEmail) { return this.http.post(`${environment.apiUrl}/wp/v2/users/lostpassword`, { user_login: usernameOrEmail }); } getPrivatePosts() { return this.http.get<any[]>(`${environment.apiUrl}/wp/v2/posts?_embed&status=private`).pipe( map(data => { for (let post of data) { if (post['_embedded']['wp:featuredmedia']) { post.media_url = post['_embedded']['wp:featuredmedia'][0]['media_details'].sizes['medium'].source_url; } } return data; }) ); } getCurrentUser() { return this.user.asObservable(); } getUserValue() { return this.user.getValue(); } logout() { this.storage.remove(JWT_KEY).then(() => { this.user.next(null); }); } }
So the idea is to sign in a user, store the token, and whenever we make a following request (in our case only to load private posts), we attach the JWT to the header of our request.
And we can do this easily by implementing an interceptor, that will load the token value and if available, add it to the headers of our request.
The following code performs exactly this operation, so we don’t need any additional JWT library and can simply create a new file at app/interceptors/jwt.interceptor.ts like this:
import { Injectable } from '@angular/core'; import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http'; import { Observable } from 'rxjs'; import { ApiService } from '../services/api.service'; @Injectable() export class JwtInterceptor implements HttpInterceptor { constructor(private api: ApiService) { } intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { let currentUser = this.api.getUserValue(); if (currentUser && currentUser.token) { request = request.clone({ setHeaders: { Authorization: `Bearer ${currentUser.token}` } }); } return next.handle(request); } }
That’s it – now we will attach the token to all requests if the user is authenticated! This means, it will not only work for the one special request that requires authentication, but all other requests to the WP API that you might make that would need authentication.
Login, Signup and Authenticated WordPress Requests
Now we basically just need to hook up our view with the functonality of our service. First of all, we need to add the ReactiveFormsModule
since we will use a reactive form
Therefore go ahead and add it to the home/home.module.ts of our app:
import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { IonicModule } from '@ionic/angular'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { RouterModule } from '@angular/router'; import { HomePage } from './home.page'; @NgModule({ imports: [ CommonModule, FormsModule, IonicModule, RouterModule.forChild([ { path: '', component: HomePage } ]), ReactiveFormsModule ], declarations: [HomePage] }) export class HomePageModule {}
With that in place we can create our form for the login or sign up credentials, and for simplicity we will simply create just one form for both cases.
Just note that you need to send all 3 values (username, email, password) if you want to register a new user!
Besides that we can integrate our service functionality and handle the results and errors of these calls. For the forgot password dialog we can use a simple alert, which is already the most exiting thing about our class.
We subscribe to the Observable of the user, and once we have a real user we will also trigger a reload of the private posts.
Go ahead and change your home/home.page.ts to:
import { Component, OnInit } from '@angular/core'; import { ApiService } from '../services/api.service'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { AlertController, ToastController } from '@ionic/angular'; @Component({ selector: 'app-home', templateUrl: 'home.page.html', styleUrls: ['home.page.scss'] }) export class HomePage implements OnInit { userForm: FormGroup; user = this.api.getCurrentUser(); posts = []; constructor( private api: ApiService, private fb: FormBuilder, private alertCtrl: AlertController, private toastCtrl: ToastController ) { this.user.subscribe(user => { if (user) { this.loadPrivatePosts(); } else { this.posts = []; } }); } ngOnInit() { this.userForm = this.fb.group({ username: ['', Validators.required], email: '', password: ['', Validators.required] }); } login() { this.api.signIn(this.userForm.value.username, this.userForm.value.password).subscribe( res => {}, err => { this.showError(err); } ); } signUp() { this.api.signUp(this.userForm.value.username, this.userForm.value.email, this.userForm.value.password).subscribe( async res => { const toast = await this.toastCtrl.create({ message: res['message'], duration: 3000 }); toast.present(); }, err => { this.showError(err); } ); } async openPwReset() { const alert = await this.alertCtrl.create({ header: 'Forgot password?', message: 'Enter your email or username to retrieve a new password', inputs: [ { type: 'text', name: 'usernameOrEmail' } ], buttons: [ { role: 'cancel', text: 'Back' }, { text: 'Reset Password', handler: (data) => { this.resetPw(data['usernameOrEmail']); } } ] }); await alert.present(); } resetPw(usernameOrEmail) { this.api.resetPassword(usernameOrEmail).subscribe( async res => { const toast = await this.toastCtrl.create({ message: res['message'], duration: 2000 }); toast.present(); }, err => { this.showError(err); } ); } loadPrivatePosts() { this.api.getPrivatePosts().subscribe(res => { this.posts = res; }); } logout() { this.api.logout(); } async showError(err) { const alert = await this.alertCtrl.create({ header: err.error.code, subHeader: err.error.data.status, message: err.error.message, buttons: ['OK'] }); await alert.present(); } }
The last missing piece is our view, which basically shows our form for data plus the according buttons to trigger the actions.
We can also add a little if/else logic using ng-template
to show either the card with our input fields, or a dummy card with the user value printed out as JSON.
Since the user object is an Observable we also need the async pipe, which makes the if statement looks kinda tricky (which it actually isn’t).
Below our cards we can also create a very basic list to show all private posts. Of course that’s just one idea and quick example of how to present it, you can also find a more detailed version in my previous WordPress tutorial!
Finish your app by changing the home/home.page.html to:
<ion-header> <ion-toolbar color="primary"> <ion-title> Devdactic Wordpress </ion-title> </ion-toolbar> </ion-header> <ion-content> <ion-card *ngIf="!(user | async); else welcome"> <ion-card-content> <form [formGroup]="userForm" (ngSubmit)="login()"> <ion-item> <ion-label position="floating">Username</ion-label> <ion-input formControlName="username"></ion-input> </ion-item> <ion-item> <ion-label position="floating">Email</ion-label> <ion-input formControlName="email"></ion-input> </ion-item> <ion-item> <ion-label position="floating">Password</ion-label> <ion-input type="password" formControlName="password"></ion-input> </ion-item> <ion-button expand="full" [disabled]="!userForm.valid" type="submit">Sign in</ion-button> <ion-button expand="full" [disabled]="!userForm.valid" type="button" color="secondary" (click)="signUp()"> Register</ion-button> <ion-button expand="full" type="button" color="tertiary" (click)="openPwReset()">Forgot password?</ion-button> </form> </ion-card-content> </ion-card> <ng-template #welcome> <ion-card> <ion-card-header> <ion-card-title>Welcome back!</ion-card-title> </ion-card-header> <ion-card-content> {{ ( user | async) | json }} <ion-button expand="full" (click)="logout()">Logout</ion-button> </ion-card-content> </ion-card> </ng-template> <ion-card *ngFor="let post of posts"> <ion-card-header> <ion-card-title [innerHTML]="post.title.rendered"></ion-card-title> <ion-card-subtitle>{{ post.date_gmt | date }}</ion-card-subtitle> </ion-card-header> <ion-card-content> <img [src]="post.media_url" *ngIf="post.media_url"> <div [innerHTML]="post.excerpt.rendered"></div> <!-- Example logic to open a details page below --> <!-- <ion-button expand="full" fill="clear" [routerLink]="['/', 'posts', post.id]" text-right>Read More...</ion-button> --> </ion-card-content> </ion-card> </ion-content>
Now go ahead and enjoy your authentication flow from Ionic with WordPress backend!
Conclusion
Adding REST API authentication to your WordPress blog isn’t that hard and helps to create powerful apps using Ionic.
Just keep in mind that the initial role of a new user might be something that has no access to private posts yet, so you would have to manually promote users in that case.
Anyway, using WordPress as a simple backend is a great alternative to other systems if you already have a WordPress blog or know a thing or two about PHP!
You can also find a video version of this tutorial below.
The post How to use WordPress API Authentication with Ionic appeared first on Devdactic.