Any mobile application that accesses remote data will need authentication at some point in time. There are many different authentication strategies out there, one of which is with Json Web Tokens (JWT) that we explored in one of my previous Node.js articles. With JWT, users can authenticate via username and password, receive a signed token back, and use that token for any future API request rather than continuing to distribute the username and password.
In this tutorial we’re going to explore how to build an Android and iOS mobile application using NativeScript and Angular that authenticates with an API and then uses a Json Web Token for future requests to that same API.
While the concepts in this tutorial can be applied towards any of your own projects, this particular tutorial is going to be based off the JWT in Node.js tutorial that I had previously written. In that tutorial we had created an API with no client front-end. As you can guess, the NativeScript application will be our front-end.
In the above animated image we have two screens, one with a form and one that displays data from a protected API endpoint. The responses displayed in the Toast notification come from the API and the data displayed on the second screen is a response from the API. What you don’t see is that after signing in on the first page you receive a Json Web Token and on the second page the token is used to obtain the message.
So let’s work towards a working example in NativeScript!
There are a few requirements that must be met before starting on this project. While there is some flexibility, it is best to try to match them as much as possible.
By now you should already have the NativeScript CLI installed. This was obtained through the Node Package Manager (NPM) which is a part of Node.js. For simplicity, it might make sense to use the Node.js Json Web Token tutorial I wrote as your backend, rather than exploring on your own. However, if you’re up for the challenge, the same rules apply if you use something else.
To keep things simple and easy to understand, we’re going to start with a fresh NativeScript project that uses Angular. This project will use various plugins and build platforms.
With the NativeScript CLI, execute the following commands:
tns create TokenProject --ng
cd TokenProject
tns platform add ios
tns platform add android
The --ng
tag indicates that we are creating an Angular with TypeScript project. I’ve added two build platforms, but it is important to note that if you’re not using a Mac with Xcode installed, you cannot add the iOS build platform.
To give this application a little flair, we’re going to be using a plugin for Toast notifications. These are the notifications that appear at the bottom of the screen for a short period of time and disappear.
From the Command Prompt or Terminal, execute the following command:
tns plugin add nativescript-toast
Toast notifications are very useful for any application because they are non-blocking on the UI. More information on them can be read about in a previous article that I wrote.
If you’re using iOS like I am, you’ll need to do a further configuration, otherwise you won’t be able to use an API that isn’t using HTTPS. As of iOS 9.0, Apple introduced what is known as App Transport Security (ATS) and it is part of an effort to protect users from unsafe remote services.
Open the project’s app/App_Resources/iOS/Info.plist file and include the following XML markup:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true />
</dict>
In a production application you won’t want the above lines in particular because the above lines allow HTTP connections to any remote service. In a production application you’ll want to define a list of allowed applications.
Android applications do not suffer from this so if you’re not using iOS then you don’t have to worry about ATS. For more information on ATS, check out a previous article I wrote about on the subject.
Now let’s worry about creating all of our application files and linking them together. This is going to be a two page mobile application, so let’s create each of the two pages.
From the command line, execute the following commands:
mkdir -p app/components/login
mkdir -p app/components/authenticated
touch app/components/login/login.ts
touch app/components/login/login.html
touch app/components/authenticated/authenticated.ts
touch app/components/authenticated/authenticated.html
If you’re not using a system that supports the mkdir
or touch
commands, don’t worry. Just create all of the above files and directories manually.
Now let’s take our business into the project’s app/app.module.ts file where we can link all these pages together with application routing.
Open the project’s app/app.module.ts file and include the following TypeScript code. Don’t worry, we’ll break it down after.
import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core";
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
import { NativeScriptHttpModule } from "nativescript-angular/http";
import { NativeScriptRouterModule } from "nativescript-angular/router";
import { NativeScriptFormsModule } from "nativescript-angular/forms";
import { AppComponent } from "./app.component";
import { LoginComponent } from "./components/login/login";
import { AuthenticatedComponent } from "./components/authenticated/authenticated";
let routes = [
{ path: "", component: LoginComponent },
{ path: "authenticated", component: AuthenticatedComponent }
];
@NgModule({
declarations: [
AppComponent,
LoginComponent,
AuthenticatedComponent
],
bootstrap: [AppComponent],
imports: [
NativeScriptModule,
NativeScriptFormsModule,
NativeScriptHttpModule,
NativeScriptRouterModule,
NativeScriptRouterModule.forRoot(routes)
],
schemas: [NO_ERRORS_SCHEMA]
})
export class AppModule { }
There are a few modules that need to be imported for each of the things we wish to accomplish:
import { NativeScriptHttpModule } from "nativescript-angular/http";
import { NativeScriptRouterModule } from "nativescript-angular/router";
import { NativeScriptFormsModule } from "nativescript-angular/forms";
The NativeScriptHttpModule
will allow us to make HTTP requests against a remote web service, in this case our JWT service. The NativeScriptRouterModule
will allow us to navigate between pages of the application and the NativeScriptFormsModule
allows us to do data binding between our HTML UI inputs and the TypeScript backend logic.
import { LoginComponent } from "./components/login/login";
import { AuthenticatedComponent } from "./components/authenticated/authenticated";
let routes = [
{ path: "", component: LoginComponent },
{ path: "authenticated", component: AuthenticatedComponent }
];
Although we haven’t created the LoginComponent
and AuthenticatedComponent
classes yet, we plan to import them and assign them to a route. The default route will have an empty path. This means that particular component will display on the screen first.
Inside the @NgModule
block we can add our components to the declarations
array and import each of our modules.
We’re not done yet though. While we have the routes to our pages defined, there is no passthrough in the actual application. Open the project’s app/app.component.html file and include the following HTML markup:
<page-router-outlet></page-router-outlet>
At this point in time the core application functionality is ready for development. All of the boilerplate and bootstrapping logic has been completed.
The first thing we want to do is create our page for authenticating the user against the remote web service. Open the project’s app/components/login/login.ts file and include the following TypeScript code:
import { Component } from "@angular/core";
import { Http, Headers, RequestOptions } from "@angular/http";
import { Router } from "@angular/router";
import * as Toast from "nativescript-toast";
import "rxjs/Rx";
@Component({
selector: "login",
templateUrl: "./components/login/login.html",
})
export class LoginComponent {
public constructor(private http: Http, private router: Router) { }
public login(username: string, password: string) {
let headers = new Headers({ "Content-Type": "application/json" });
let options = new RequestOptions({ headers: headers });
this.http.post("http://localhost:3000/authenticate", JSON.stringify({ username: username, password: password }), options)
.map(result => result.json())
.subscribe(result => {
this.router.navigate(["authenticated"], { queryParams: { jwt: result.token } });
}, error => {
Toast.makeText(error.json().message).show();
});
}
}
In the above code we import several Angular components as well as RxJS and the Toast plugin that we downloaded. In the constructor
method of the LoginComponent
class we inject each of the Angular services so they can be used throughout the page.
This brings us to our core method, the login
method:
public login(username: string, password: string) {
let headers = new Headers({ "Content-Type": "application/json" });
let options = new RequestOptions({ headers: headers });
this.http.post("http://localhost:3000/authenticate", JSON.stringify({ username: username, password: password }), options)
.map(result => result.json())
.subscribe(result => {
this.router.navigate(["authenticated"], { queryParams: { jwt: result.token } });
}, error => {
Toast.makeText(error.json().message).show();
});
}
Because we’re doing a POST request, we need to define the content body being sent. If we don’t, the receiving server will think it is text data, but in reality we want it to be JSON data. With the request we are passing the header information and a serialized JSON object containing the username and password of the user.
The request uses RxJS so we can work with the returned observable. First we transform the response into a JSON object, and subscribe to it. In the event there is an error, we’ll show it in a Toast notification, otherwise we’ll pass the JWT token as a query parameter to the next page.
So what does the UI look like for this particular form?
Open the project’s app/components/login/login.html file and include the following HTML markup:
<ActionBar title="{N} JWT Example"></ActionBar>
<StackLayout verticalAlignment="center">
<Label text="Sign into the application" class="h2 text-center"></Label>
<StackLayout class="form">
<StackLayout class="input-field">
<Label class="label" text="Username:"></Label>
<TextField #username class="input input-border" autocorrect="false" autocapitalizationType="none"></TextField>
</StackLayout>
<StackLayout class="input-field">
<Label class="label" text="Password:"></Label>
<TextField #password class="input input-border" secure="true" autocorrect="false" autocapitalizationType="none"></TextField>
</StackLayout>
<StackLayout class="input-field">
<Button text="Login" (tap)="login(username.text, password.text)" class="btn btn-primary btn-active w-full"></Button>
</StackLayout>
</StackLayout>
</StackLayout>
You’ll notice that we have an action bar and a core layout that is centered vertically. All of the CSS class names are coming from the NativeScript Theme package that is bundled with every application.
However, what is important here is the <TextField>
and <Button>
Angular Template tags. For form input we are using variables and passing them into the tap event of the page button.
Now that we have the Json Web Token from the authentication process, we can take that information to query for data on the second page. The token will be used to get further API data. Without it the API requests will fail.
Open the project’s app/components/authenticated/authenticated.ts file and include the following TypeScript code:
import { Component, OnInit } from "@angular/core";
import { Http, Headers, RequestOptions } from "@angular/http";
import { ActivatedRoute } from "@angular/router";
import "rxjs/Rx";
@Component({
selector: "authenticated",
templateUrl: "./components/authenticated/authenticated.html",
})
export class AuthenticatedComponent implements OnInit {
public content: string;
public constructor(private http: Http, private route: ActivatedRoute) {
this.content = "";
}
public ngOnInit() {
this.route.queryParams.subscribe(params => {
let headers = new Headers({ "Authorization": "Basic " + params["jwt"] });
let options = new RequestOptions({ headers: headers });
this.http.get("http://localhost:3000/protected", options)
.map(result => result.json())
.subscribe(result => {
this.content = result.message;
});
});
}
}
Looks familiar doesn’t it? Well, not exactly.
In the AuthenticatedComponent
class we have a public variable that will be bound to the UI of the application. This variable is initialized in the constructor
method as well as certain service injections.
Because we want the data to appear at load, we use the ngOnInit
method rather than trying to load it inappropriately in the constructor
method. In the ngOnInit
method we construct an authentication header using the passed JWT token, at which point we make the request. The response of the request will be rendered to the screen.
Open the project’s app/components/authenticated/authenticated.html file and include the following HTML markup:
<ActionBar title="{N} Member Content">
<NavigationButton text="Back"></NavigationButton>
</ActionBar>
<StackLayout>
<Label text="{{ content }}" class="text-center"></Label>
</StackLayout>
The above HTML is basic, but all we really wanted to do was display the public variable from the TypeScript file. This variable contains whatever the protected API endpoint provided us with.
So let’s give this project a run. From the NativeScript CLI, execute the following command to build and run the application on an emulator:
tns emulate android
If you were using an iOS emulator, you could switch out the platform of the above command.
Not interested in running through the entire tutorial? You can download the full source code here. After downloading the source code and extracting it, execute the following:
tns install
The above command will install all the project dependencies, at which point, the project can be built and run.
You just saw how to create a NativeScript application with Angular that communicates with a Json Web Token (JWT) protected API. By obtaining a valid JWT with our credentials we could then use the token to authenticate on further endpoints. This particular tutorial used a Node.js JWT example that I write previously, but it could be expanded to whatever you’d like to use.
If you’d like to give this application a spin, the full project source code can be obtained here. The Node.js source code can be obtained from that particular example as well.