Sunday, September 11, 2016

Build JWT authentication server with Node.js, Express and MySQL

Node.js is based on JavaScript and V8 JavaScript Engine (an open source JavaScript engine developed by The Chromium Project for the Google Chrome web browser). It supports non-blocking I/O because it is running in single process and dedicated CPU. You can spawn many Node.js processes corresponding with number of CPU cores in your machine. It is unfair to say that Node.js is run faster vs. other web servers because it is very depended on complex of your server application. However in many test cases, they shows that Node.js is very fast especially in running web service (although its startup time is a little bit slow) and how it is easy in coding.

This article is inspired from my previous article using the code from Auth0 for JWT authentication server which users are in memory array. Now I want to rewrite by using MySQL to store users and another purpose is helping beginner of Node.js can get start with Node.js + Express + MySQL.

If you don't know how to start a Node.js application, I recommend you read the popular article "Node.js Applications with VS Code" in VS Code. Assuming that you already read that article, we will start Node.js application with Express Generator as the following steps:

#express mysql-jwt-auth
#cd mysql-jwt-auth
#npm install
#code .

The last command will open mysql-jwt-auth application in VS Code for editing. You will have generated codes like below picture:


OK, now we'll add some necessary modules into this app:
#npm i cors --save
#npm i lodash --save
#npm i jsonwebtoken --save
#npm i express-jwt --save
#npm i mysql --save

After that, open app.js file and add below lines:
var cors = require('cors'); //after the line var bodyParser = require('body-parser');
app.use(cors()); //after the line app.use(logger('dev'));

Change the line:
app.use('/users', users);  ==> app.use(users);

Open your MySQL database and create table users and table quotes as the following:
CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(20) NOT NULL,
  `password` varchar(20) NOT NULL,
  `email` varchar(50) NOT NULL,
  PRIMARY KEY (`id`,`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


INSERT INTO `users`(`id`,`username`,`password`,`email`) VALUES (1,'gonto','gonto','gonto@me.com');

CREATE TABLE `quotes` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `content` varchar(150) NOT NULL,
  `private` tinyint(1) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


Add db.js file into root folder (same level with app.js):
var mysql = require('mysql');
var pool  = null;

exports.connect = function() {
  pool = mysql.createPool({
    host     : 'myhost',
    user     : 'myuser',
    password : 'mypassword',
    database : 'mydb'
  });
}

exports.get = function() {
  return pool;
}


Add below lines to bin/www file (after line: var http = require('http')):
var db = require('../db');
db.connect();


Now we have the db ready for using. Add new file config.json into root folder with content below:
{
  "secretKey": "don't share this key"
}


The above file will keep a secret key for encoding user token after logged in. Open routes/user.js file and replace by new code below:
var express = require('express'),
    _       = require('lodash'),

    config  = require('../config'),
    jwt     = require('jsonwebtoken')
    db      = require('../db');

var app = module.exports = express.Router();
var secretKey = "don't share this key";
function createToken(user) {
  return jwt.sign(_.omit(user, 'password'), config.secretKey, { expiresIn: 60*60*5 });
}

function getUserDB(username, done) {
  db.get().query('SELECT * FROM users WHERE username = ? LIMIT 1', [username], function(err, rows, fields) {
    if (err) throw err;
    done(rows[0]);
  });
}

app.post('/user/create', function(req, res) { 
  if (!req.body.username || !req.body.password) {
    return res.status(400).send("You must send the username and the password");
  }

  getUserDB(req.body.username, function(user){
    if(!user) {
      user = {
        username: req.body.username,
        password: req.body.password,
        email: req.body.email
      };
      db.get().query('INSERT INTO users SET ?', [user], function(err, result){
        if (err) throw err;
        newUser = {
          id: result.insertId,
          username: user.username,
          password: user.password,
          email: user.email
        };
        res.status(201).send({
          id_token: createToken(newUser)
        });
      });
    }
    else res.status(400).send("A user with that username already exists");
  });
});

app.post('/user/login', function(req, res) {
  if (!req.body.username || !req.body.password) {
    return res.status(400).send("You must send the username and the password");
  }

  getUserDB(req.body.username, function(user){
    if (!user) {
      return res.status(401).send("The username is not existing");
    }

    if (user.password !== req.body.password) {
      return res.status(401).send("The username or password don't match");
    }

    res.status(201).send({
      id_token: createToken(user)
    });
  });
});

app.get('/user/check/:username', function(req, res) {
  if (!req.params.username) {
    return res.status(400).send("You must send a username");
  }

  getUserDB(req.params.username, function(user){
    if(!user) res.status(201).send({username: "OK"});
    else res.status(400).send("A user with that username already exists");
  });
});


At this time, we can use above code for authenticating user. Key the command npm start and use Postman tool to check what the server response. Here is my example:

That's fine, so we are going to add routes/quotes.js file to illustrate for anonymous request and authenticated request in reading quotes. This file has simple content as below:
var express = require('express'),
    jwt     = require('express-jwt'),
    config  = require('../config'),
    db      = require('../db');

var app = module.exports = express.Router();
var jwtCheck = jwt({
  secret: config.secretKey
});

function getPublicQuotesDB(done){
    db.get().query('SELECT * FROM quotes WHERE private=0', function(err, rows) {
        if (err) throw err;
        done(rows);
    });
}

function getPrivateQuotesDB(done){
    db.get().query('SELECT * FROM quotes WHERE private=1', function(err, rows) {
        if (err) throw err;
        done(rows);
    });
}

app.get('/api/public/quote', function(req, res) {
  getPublicQuotesDB(function(result) {
      res.status(200).send(result);
  });
});

app.use('/api/private', jwtCheck);
app.get('/api/private/quote', function(req, res) {
  getPrivateQuotesDB(function(result) {
      res.status(200).send(result);
  });
});


Don't forget to add below lines to app.js file to include above quotes.js:
var quotes = require('./routes/quotes');
app.use(quotes);

OK, let use Postman tool to check if the api gets quotes working as expected. Here are my results:



As you can see, request /api/public/quote works fine. But you will get a complain "No authorization token was found" if you request /api/private/quote. When you add Authorization header with your token for it, it runs ok soon (last picture).

That's all for tonight. Bye!
You can find source code on my GitHub.

Monday, August 22, 2016

MS SQL: using BCP + Powershell + Bulk Copy to sync your table to another server

The usage scenario is: you have a huge log table (or some tables like that) and you want to sync it from your production server to another server (slaver server) for analyzing daily. This will help to reduce workload of production server in analyzing as also as backup your data on slaver server. You even can delete old data (not used more) on your production server to minimize the size of production data.

Below steps to setup this sync job:

1. Use BCP to export & import data
On production server, run bcp command to export data, for example:
#bcp "SELECT * FROM log_table" queryout "C:\log_table.bcp" -N -S "PRODUCTION_SERVER_NAME" -d "DB_NAME" -U "USER_NAME" -P "PASSWORD"

After that, upload this log_table.bcp file to your slaver server, create a log_table having same structure as on production server, then run a command like example below to import data to your log_table:
#bcp "log_table" IN "E:\log_table.bcp" -N -S "SERVER_NAME" -d "DB_NAME" -U "USER_NAME" -P "PASSWORD"

2. Make Powershell script to bulk copy new data from production server to slaver server
Below is my sample script:

#Set-ExecutionPolicy Unrestricted
#2 params needed to provide for the script
Param (
[parameter(Mandatory = $true)]
[string] $LogTable,
[parameter(Mandatory = $true)]
[int] $Syncrows
)


$SrcServer = "PRODUCTION_SERVER"
$SrcDatabase = "PRODUCTION_DB"
$SrcUser = "USER_NAME"
$SrcPwd = "PASSWORD"
$SrcTable = $LogTable


$DestServer = "SLAVER_SERVER"
$DestDatabase = "SLAVER_DB"
$DestUser = "USER_NAME"
$DestPwd = "PASSWORD"
$DestTable = $LogTable


#create connection string
Function ConnectionString ([string] $ServerName, [string] $DbName, [string] $User, [string] $Pwd)
{
    "Server=$ServerName;uid=$User;pwd=$Pwd;Database=$DbName;Integrated Security=False;"
}


# executes a query and populates the $datatable with the data
function ExecuteSqlQuery ($ConnectionString, $SQLQuery) {
    $Connection = New-Object System.Data.SQLClient.SQLConnection
    $Connection.ConnectionString = $ConnectionString
    $Connection.Open()
    $Command = New-Object System.Data.SQLClient.SQLCommand
    $Command.Connection = $Connection
    $Command.CommandText = $SQLQuery
    $Reader = $Command.ExecuteReader()
    $Datatable = New-Object System.Data.DataTable
    $Datatable.Load($Reader)
    $Connection.Close()
    $Connection.Dispose()
    return ,$Datatable #work arround to get right datatable after unrolling arrays/collections
}


#get max id of last synced rows
$DestConnStr = ConnectionString $DestServer $DestDatabase $DestUser $DestPwd
$Query = "SELECT MAX(Pid) FROM " + $DestTable
$resultsDataTable = ExecuteSqlQuery $DestConnStr $Query
$LastRow = $resultsDataTable.Rows[0][0]


#bulk copy
$SrcConnStr = ConnectionString $SrcServer $SrcDatabase $SrcUser $SrcPwd
$SrcConn  = New-Object System.Data.SqlClient.SQLConnection($SrcConnStr)
if($Syncrows -eq 0) {
    $CmdText = "SELECT * FROM $SrcTable WHERE row_id>$LastRow"
}
else {
    $CmdText = "SELECT TOP ($Syncrows) * FROM $SrcTable WHERE row_id>$LastRow"
}
#Write-Host "$CmdText"
$SqlCommand = New-Object system.Data.SqlClient.SqlCommand($CmdText, $SrcConn)
$SrcConn.Open()
[System.Data.SqlClient.SqlDataReader] $SqlReader = $SqlCommand.ExecuteReader()
Try
{
   $logmsg = "$LogTable : Bulk copy completed!"
   $bulkCopy = New-Object Data.SqlClient.SqlBulkCopy($DestConnStr, [System.Data.SqlClient.SqlBulkCopyOptions]::KeepIdentity)
   $bulkCopy.DestinationTableName = $DestTable
   $bulkCopy.BatchSize = 100 #rows
   $bulkCopy.BulkCopyTimeout = 1800 #seconds
   $bulkCopy.WriteToServer($SqlReader)
}
Catch [System.Exception]
{
   $ex = $_.Exception
   $logmsg = $ex.Message
}
Finally
{
   $currenttime = Get-Date -format s
   Add-Content D:\logstatus.txt "`n$currenttime $logmsg"
   $SqlReader.Close()
   $SrcConn.Close()
   $SrcConn.Dispose()
   $bulkCopy.Close()
}


The above script will use BulkCopy to copy rows having id > id synced. You can limit number of rows for syncing via $Syncrows param. If $Syncrows=0, it will get all new rows to copy.

3. Create task schedule for copying new data
Use Task Scheduler to create a new task for running the script file, you can select your schedule as your demand. Remember to add below line in Action setting for passing Execution Policy:

-ExecutionPolicy Bypass D:\synctbl.ps1 -LogTable log_table -Syncrows 10000


That's all. Happy coding! Any questions?


Friday, August 12, 2016

CodeIgniter: Build JWT authentication REST server

1. About CodeIgniter

CodeIgniter (CI) was created by EllisLab, and is now a project of the British Columbia Institute of Technology. its website is https://www.codeigniter.com.

It is a light weight PHP framework but very flexible and fast. You can read more info about CI and why it is good for you at here. Beside that, from my experience, I see CI is good for:
  • Building REST / RESTFull APIs
  • Building concept apps quickly
  • Creating beautiful and fast web apps with AngularJS or React easily
2. Installation

You can read its installation instruction on its website. Here I just high light some important notes:

2.1 Make friendly URL without index.php:
+ Apache: create .htaccess file in root folder with below content:
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1 [L]


+ IIS: create web.config file in root folder with below content:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
 <system.webServer>
  <rewrite>
    <rules>
   <rule name="CI Rule" stopProcessing="true">
     <match url="^(.*)$" ignoreCase="false" />
     <conditions>
    <add input="{REQUEST_FILENAME}" matchType="IsFile" ignoreCase="false" negate="true" />
    <add input="{REQUEST_FILENAME}" matchType="IsDirectory" ignoreCase="false" negate="true" />
    <add input="{URL}" pattern="^/favicon.ico$" ignoreCase="false" negate="true" />
     </conditions>
     <action type="Rewrite" url="index.php/{R:1}" appendQueryString="true" />
   </rule>
    </rules>
  </rewrite>
 </system.webServer>
</configuration>


2.2 Set up multiple domains using HTTP or HTTPS
Read here: https://www.codeigniter.com/userguide3/installation/upgrade_303.html

2.3 Handling errors and show customized 404 page
Read here: https://www.codeigniter.com/userguide3/general/errors.html?highlight=#show_404

3. Folder structure, app flow and URI
CI has top folder structure as the following:
Almost your code will be under application folder, which has below folders:
The controller folder contains classes which can be invoked via URIs. To understand roles of folders under application, let see the flow of CI application:
Read here for more info about its app flow.

Its URI segment is based on this rule: website.com/class/function/var, in which:
  • class: a controller class, it is in a file of application/controllers folder
  • function: an invoked function in above class
  • var: a variable passed to above function, it is optional in the URI
Read more at: https://www.codeigniter.com/userguide3/general/urls.html

4. PHP JWT classes for CI
JSON Web Tokens is new industry standard for securely transmitting information between parties (e.g. client and server). It is becoming popular in authenticating, especially in mobile apps world.

To provide this feature for CI, we need to create some classes for implementing JWT standard. I don't want to reinvent the wheel, so let copy JWT classes from php-jwt (these classes are in src folder) into application/libraries folder of your CI.

5. REST server, users and authentication:
Next step, we need to build a REST server which will use php-jwt to authenticate and create access token after user logins successfully.

Luckily, there is a good REST server built by chriskacerguis. Please see the installation guide on its Github to install into your project. In the case you want to allow CORS, let open application/config/rest.php then set:

$config['check_cors'] = TRUE;
...
$config['allow_any_cors_domain'] = TRUE;

Now we have 2 libraries: php-jwt and REST server ready for using. Then we need a model to manage our users database. Let create your users table firstly:

CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(20) NOT NULL,
  `password` varchar(20) NOT NULL,
  `email` varchar(50) NOT NULL,
  PRIMARY KEY (`id`,`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


INSERT INTO `users`(`id`,`username`,`password`,`email`) VALUES (1,'gonto','gonto','gonto@me.com');

Create Users_model in application/models (remember config your DB in application\config\database.php)

class Users_model extends CI_Model {
    public function __construct() {
        $this->load->database();
    }

    public function login($username, $password) {
        $this->db->select('*');
  $this->db->from('users');
  $this->db->where('username', $username);
  $query = $this->db->get();
        if ($query->num_rows() == 1) {
            $result = $query->result();
            return $result[0]->id;
        }
        return false;
    }
}


Make an API for checking user login (User.php) in application/controllers/api:

require_once APPPATH . '/libraries/REST_Controller.php';
require_once APPPATH . '/libraries/JWT.php';

use \Firebase\JWT\JWT;
class User extends REST_Controller {
    public function __construct() {
        parent::__construct();
        $this->load->model('Users_model');
    }

    public function login_post() {
        $username = $this->post('username');
        $password = $this->post('password');
        $invalidLogin = ['invalid' => $username];
        if(!$username || !$password) $this->response($invalidLogin, REST_Controller::HTTP_NOT_FOUND);
        $id = $this->Users_model->login($username,$password);
        if($id) {
            $token['id'] = $id;
            $token['username'] = $username;
            $date = new DateTime();
            $token['iat'] = $date->getTimestamp();
            $token['exp'] = $date->getTimestamp() + 60*60*5;
            $output['id_token'] = JWT::encode($token, "my Secret key!");
            $this->set_response($output, REST_Controller::HTTP_OK);
        }
        else {
            $this->set_response($invalidLogin, REST_Controller::HTTP_NOT_FOUND);
        }
    }
}


Yeah! We can login and get the token now!!!. Let check it on Postman tool:

That's all for today!

In next article, I'll present an Ionic 2 app which uses this REST server for authenticating with JWT.

Happy coding!




Thursday, August 4, 2016

Genymotion - VirtualBox: Fix error "VT-x/AMD-V hardware acceleration is not available on your system"

Genymotion (run on top VirtualBox) is one of the best emulators for Android. It is preferred by Android's developers because it is light and has high speed for testing apps in Android.

In the case of you get error: "VT-x/AMD-V hardware acceleration is not available on your system" when running a 64 bit virtual device, the following steps can help you solve this problem:

1. Your CPU (Intel/AMD) must support 64 bit with virtualization technologies. You must enable it in the BIOS setting. To know where, let dig on searching your BIOS manual. For example in the BIOS of my PC, it is in Configuration menu:

2. If you are using Windows 7/8/10, let turn off Hyper-V feature:
3. Restart your PC, open Genymotion and download your favorite 64 bit virtual device for using.

Happy coding!




Tuesday, July 12, 2016

Ionic 2: FormBuilder and Validator

In the article "Learn Ionic 2 with Authentication by JWT", you may know how to make a login page and navigate between pages after login.

In this article, we are going to improve the Signup form of Login page with FormBuilder and Validation which are strong weapons of Angular 2.

1. Add an URL to check existing user in the authentication server
Open the file user-routes.js in the authentication server, add new function to check a username if existing:
app.post('/check', function(req, res) {
  var userScheme = getUserScheme(req);
  if (!userScheme.username) {
    return res.status(400).send("You must send a username");
  }

  if (_.find(users, userScheme.userSearch)) {
   return res.status(400).send("A user with that username already exists");
  }

  res.status(201).send({username: "OK"});
});


Then start the server by using the command:
#node server.js

2. Mofidy file app/providers/auth-service/auth-service.ts
Open this file and add new member and method (function) to check a user as the following:
CHECK_URL = "http://localhost:3001/check";
check(user) {
        return new Promise((resolve, reject) => {
            this.http.post(this.CHECK_URL, JSON.stringify({username: user}), { headers: this.contentHeader })
                .map(res => res.json())
                .subscribe(
                    data => {
                        resolve(data)
                    },
                    err => {
                        reject(err)
                    }
                );
        });
}


3. Add new file app/validators/password.ts
It is used to validate the password input in Signup form. This file has content as below:
import {Control} from '@angular/common';
export class PasswordValidator {
    static checkPassword(control: Control) {
        // password must be between 4 and 10 characters and have at least 1 number
        if (control.value.match(/^(?=.*[0-9])[a-zA-Z0-9!@#$%^&*]{4,10}$/)) {
            return null;
        } else {
            return { 'invalidPassword': true };
        }
    }
}


4. Modify app/pages/login/login.ts
This file is modified as below:
import {Page, NavController} from 'ionic-angular';
import {FormBuilder, ControlGroup, Validators } from '@angular/common';
import {AuthService} from '../../providers/auth-service/auth-service';
import {WorkPage} from '../work/work';

import {PasswordValidator} from  '../../validators/password';
@Page({
  templateUrl: 'build/pages/login/login.html',
})
export class LoginPage {
    authType: string = "login";
    usernameIsValid: boolean = false;
   
   
constructor(private auth: AuthService, private nav: NavController, private formBuilder: FormBuilder) {
        this.signupCreds = formBuilder.group({
          'username': ['', Validators.compose([Validators.required, Validators.pattern('[a-zA-Z]*')])],
          'password': ['', PasswordValidator.checkPassword],
          'email': ['', Validators.required]
        });
    }


    checkUsername(username){
        if(username.length < 4) { //username must have at least 4 characters
          this.usernameIsValid = false;
          return;
        }
        this.auth.check(username.toLowerCase()).then(
          (success) => {
            this.usernameIsValid = true;
          },
          (err) => {
            this.usernameIsValid = false;
          }
        );
    }

   
    login(credentials) {
        this.auth.login(credentials).then(
          (success) => {
            this.nav.setRoot(WorkPage);
          },
          (err) => console.log(err)
        );
    }
   
    signup(credentials) {
        this.auth.signup(credentials).then(
          (success) => {
            this.nav.setRoot(WorkPage);
          },
          (err) => console.log(err)
        );
    }
}


The new BLUE codes are added / modified. Pay your attention on the constructor which I use FormBuilder to created signupCreds form and bind its controls to Validators. As you can see the password control use PasswordValidator.checkPassword to validate input password.

4. Modify app/pages/login/login.html
Let modify the signup form as the following:
<form *ngSwitchWhen="'signup'" [ngFormModel]="signupCreds" (ngSubmit)="signup(signupCreds.value)">
        <ion-item>
          <ion-label>Username</ion-label>
          <ion-input (change)="checkUsername(signupCreds.controls.username.value)" type="text" ngControl="username"></ion-input>
        </ion-item>
        <p *ngIf="signupCreds.controls.username.hasError('required') && signupCreds.controls.username.touched" danger>Username is required</p>
        <p *ngIf="signupCreds.controls.username.touched && !usernameIsValid" danger>User is invalid or taken</p>
        <p *ngIf="signupCreds.controls.username.touched && usernameIsValid" secondary> User is OK</p>
       
        <ion-item>
          <ion-label>Password</ion-label>
          <ion-input type="password" ngControl="password"></ion-input>
        </ion-item>
        <p *ngIf="!signupCreds.controls.password.valid && signupCreds.controls.password.touched" danger>Password is not meet standards</p>
        <p *ngIf="signupCreds.controls.password.valid && signupCreds.controls.username.touched" secondary> Password is good</p>


        <ion-item>
          <ion-label>Email</ion-label>
          <ion-input type="email" placeholder="Your email" ngControl="email"></ion-input>
        </ion-item>
        <p *ngIf="signupCreds.controls.email.hasError('required') && signupCreds.controls.email.touched" danger>Email is required</p>
       
        <div padding>
          <button block [disabled]="!usernameIsValid || !signupCreds.controls.password.valid || !signupCreds.controls.email.valid" type="submit">Signup</button>
        </div>
</form>


While email uses a standard Validator and password uses a customized Validator, username uses event change of the control to validate an input username. This will help to reduce number of calls to the authentication server to check the input username. If not, it will call the authentication server when keying every character in Username box.

Beside that, in this new form, you can not click Signup button if any control is invalid. Each control will have a message to inform you it is OK or not.

Now you can run ionic serve then try this new signup form. Here is my screen:

Hope this article can help you on developing your app on Ionic 2.
Happy coding! Cheers.


Subscribe to RSS Feed Follow me on Twitter!