Our website is made possible by displaying online advertisements to our visitors. Please consider supporting us by disabling your ad blocker.

Password Hashing and JWTs for NativeScript Apps with an Express.js Backend

TwitterFacebookRedditLinkedInHacker News

When building an application that allows users to have accounts, you have to ensure that access to these accounts is secure. When building a user account system, an important factor to keep in mind is how passwords are stored. Storing passwords as plain text is a complete rookie move that leaves your users vulnerable to all sorts of data breaches.

The best way to protect passwords is to employ hashing and salting and in this tutorial, we’ll show you exactly how to do this. We’ll also show you how to generate JSON Web Tokens (JWT) on a Node.js server backend that can be used to authenticate and authorize users, as well as how to store those tokens on the client NativeScript application.

TL;DR

If you prefer video, you can watch the video below which is an excerpt from my course on Securing NativeScript Applications.

Code Files

This tutorial goes through a few topics that are covered in my Securing NativeScript Applications course. We use the demo app that was used in that course, so if you want to follow along, you can find the code files on GitHub. At the end of each of the three sections below, your code should be similar to the 03_01_end, 03_02_end and 03_03_end folders, respectively. We will be working on two applications: securityapp which is the client NativeScript app and securityserver which is our Node.js server.

Hashing and Salting

To start off, we are going to use a third-party library (bcryptjs) to perform hashing and salting of passwords before they are saved.

As a side note, Nic Raboy has a related post about Hashing Password Data in MongoDB with Mongoose and Bcrypt.

On the server app securityserver, install the bcryptjs package and the TypeScript type definitions for it:

npm i bcryptjs --save
npm i @types/bcryptjs --save-dev

bcrypt is a password hashing function that uses very strong cryptography algorithms. It incorporates a salt to protect against rainbow table attacks and gives you the flexibility of changing the number of iterations it goes through when encrypting passwords. The higher the number, the more resistant the result will be to brute-force attacks, but this might come at the expense of user experience since the increased computation makes the signup / signin process slower for users. You have to walk that fine line between user experience and security.

Next, we add the following two functions to the app/shared/util.ts file that will handle password hashing and verification.

import { genSaltSync, hashSync, compareSync } from 'bcryptjs';

export function hashPassword(password: string): string {
    const salt = genSaltSync(12);
    const hashedPassword = hashSync(password, salt);
    return hashedPassword;
}

export function verifyPassword(passwordAttempted, hashedPassword): boolean {
    return compareSync(passwordAttempted, hashedPassword);
}

...

hashPassword() will be called during user registration to hash the password before the user record is saved. Here, we first generate a salt with genSaltSync(). There is an asynchronous variant of this function genSalt() that you can use instead. We set the rounds to use to 12. This is the number of times the algorithm will run. The default rounds is 10 which means that the internal function will process 1,024 times. For every increment to the number of rounds, the number of times the function is processed doubles. So if you go up to 11, the function will process twice as many times, i.e. 2048, for 12 rounds, it will process 4096 times, and so on. The maximum rounds you can set is 16 which will process 65,536 times which will take about 10 seconds which will result in very poor user experience, so you have to pick an appropriate number that will keep the passwords secure while not being such a hit on user experience.

To hash the password that was passed into the function, we use hashSync(). This also has an asynchronous variant hash(). The function takes the password string and salt and returns a hashed password string.

The second function verifyPassword() will be called when a user attempts to log in. It takes a string (the user’s input) and a hash to test it against (the hashed password saved in the DB). It then compares them with compareSync() and returns a boolean.

With those two set up, let’s make use of them in the Users controller app/users/controller/index.ts where we have our signup and signin logic.

Let’s start with user account creation:

export function registerUser(req: Request, res: Response) {

    const hashedPassword = hashPassword(req.body.password);

    const userData = {
        email: req.body.email,
        password: hashedPassword
    };

    createUser(userData);

    return res.json({ message: 'User created!' });
}

The above takes the input from the password field and passes it to hashPassword() and adds the hashed result to an object holding the user’s data. We then pass this object to createUser(). This is where you would normally save the user data to the database, but for this demo app createUser() just writes the user information to a file app/data/users.json. You can run the client application and try signing up to an account. Make sure to keep the server running.

And now for user login:

export function loginUser(req: Request, res: Response) {
    const user = getUser(req.body.email);

    if (user) {
        const passwordMatches = verifyPassword(req.body.password, user.password);
        if (passwordMatches) {
            return res.json({ message: 'User logged in!' });
        } else {
            res.status(403).json({
                message: 'Wrong email or password.'
            });
        }
    } else {
        res.status(403).json({
            message: 'Wrong email or password.'
        });
    }

}

Here, we first check if a user with the submitted password exists before trying to authenticate them. If they do, we pass the submitted password and the hashed password for that particular account to verifyPassword() and either authenticate them or show them an error message, depending on the result.

As you can see, securing and verifying passwords is simplified with various libraries that are available for us to use. Now onto the next topic…

Generating JWT

A JSON Web Token (JWT) is a JSON-based open standard for creating access tokens that assert some number of claims. For instance, a server could generate a token that has the claim “logged in as user and has authorization to perform X” and provide that to a client. The client could then use that token to prove that it is logged in as a user and has rights to perform X.

We are going to see how to generate such a token on our Node.js server, then in the next lesson, we’ll see how the token can be stored on the client application.

For increased security, generated tokens will be signed by a secret stored on the server, which will then be used to verify tokens that are submitted to the server. Any token that wasn’t signed with the secret will fail verification.

We’ll save the secret in an environment variable. For the server app to be able to read environmental variables, install the following package:

npm i dotenv --save

Next, we create a .env file on the server to store our secret.

SECRET=my-special-secret

We then load the environment variables into the app by adding the following to server.ts:

require('dotenv').load();

To generate the JWTs, we are going to use the jsonwebtoken library. Install it and its TypeScript definitions:

npm i jsonwebtoken --save
npm i@types/jsonwebtoken --save-dev

In utils.ts, add the following function:

import { User } from './models/user.model';
import { sign } from 'jsonwebtoken';

...

export function createToken(user: User) {
    const payload = {
        id: user.id,
        email: user.email
    };
    const secret = process.env['SECRET'];

    const signedToken = sign(payload, secret, {
        algorithm: 'HS256',
        expiresIn: '1h'
    });
    return signedToken;
}

To create a token, we use the sign() function which takes a payload (data that will be stored in the JWT), a secret that will be used to sign the JWT and some options that will determine various characteristics that the JWT will have. Here we specify that the JWT should be signed with the HS256 algorithm and that it should expire in an hour. JWTs cannot be unauthenticated, but they can expire. To protect your users, you shouldn’t put a very long expiry period. Remember, anyone who gets hold of the JWT can get authenticated, so you shouldn’t leave valid JWTs sticking around for very long. If a user requires prolonged access, you can always generate a new JWT and send it to the client.

We can now use this function in our User controller to generate a token when the user logs in.

export function loginUser(req: Request, res: Response) {
    const user = getUser(req.body.email);


    if (user) {
        const passwordMatches = verifyPassword(req.body.password, user.password);
        if (passwordMatches) {

            const jwt = createToken(user);

            return res.json({ message: 'User logged in!', access_token: jwt });
        } else {
            res.status(403).json({
                message: 'Wrong email or password.'
            });
        }
    } else {
        res.status(403).json({
            message: 'Wrong email or password.'
        });
    }
}

After the user has been authenticated, we generate a token for them and send it back in the response object.

On the client app securityapp, you can verify that the token does get sent by logging it out:

import { tap } from 'rxjs/operators';

...

public login(user: AuthUser): Observable<any> {
    return this.http.post(`${Config.apiUrl}/login`, {
        ...user
    }).pipe(
        tap(result => {
            console.log('token received: ' + result.access_token)
        })
    );
}

You can verify that it’s a valid token and decode its data on jwt.io.

Storing JWT on the Client

Right now, we can generate tokens and send them back to authenticated clients. On the client application, you have to be careful how you store tokens. Anyone who gets ahold of a valid token can use it to access the permissions it guarantees. We’ll see how we can save tokens locally on the client.

A typical way to do local storage in a NativeScript application is by using the application-settings module. We are already using the module to store a flag that lets us know if the user is authenticated or not:

public get isAuthenticated() {
    return appSettingsModule.getBoolean(AUTHENTICATED_KEY);
}

public set isAuthenticated(val: boolean) {
    appSettingsModule.setBoolean(AUTHENTICATED_KEY, val);
}

Below, we use it to store and retrieve the token:

const ACCESS_TOKEN_KEY = 'ACCESS_TOKEN_KEY';
...

private setToken(accessToken: string): void {
    appSettingsModule.setString(ACCESS_TOKEN_KEY, accessToken);
}

public getToken(): string {
    return appSettingsModule.getString(ACCESS_TOKEN_KEY);
}

Now after logging in we can set the token:

public login(user: AuthUser): Observable<any> {
    return this.http.post(`${Config.apiUrl}/login`, {
        ...user
    }).pipe(
        tap(result => {
            console.log('token received: ' + result.access_token);
            this.setToken(result.access_token);
        })
    );
}

That’s it! The token now gets saved on the client.

I hope you found the article useful. For more on NativeSript security, be sure to check out the Securing NativeScript Applications course. We go deeper into securing NativeScript applications. We start off with unsecured client and server apps, and step-by-step add more security into the apps. In this tutorial, we looked at one way of saving tokens on the client—App Settings—but this is usually not the most secure way. In the course, we explore other device storage mechanisms and their security implications.

Let me know if you enjoyed this tutorial on Twitter: @digitalix or comment down below. Use the hashtag #iScriptNative on Twitter with your NativeScript questions and I can try and answer you on my YouTube channel, where I post videos about NativeScript.

Alex Ziskind

Alex Ziskind

Alex is a NativeScript Developer Expert and owner of a Washington DC based software firm, Nuvious, since 2010. He is also a partner with nStudio where he mentors budding NativeScript developers. Alex also really loves to spread knowledge by teaching and training teams of developers, whether it’s in person on through video courses on NativeScripting.com. Connect with Alex on Twitter @digitalix.