Registration & Login Function
Introduction
In today’s lesson, I’m going to implement registration and login functions. Each function will be available as an API endpoint in the app server built in a previous post.
To follow along, take a look at these posts:
Development Environment
Node Setup
App Server
Database Creation
API Structure
At this point, I’ve setup my node.js development environment, created an app server with a health check API endpoint, and finally setup a database with an accounts table inside it. I’m ready to write the registration & login handlers.
I’m going to open my app server file, base-server.js, in VS Code. It looks like this (as plain text outside the IDE):
'use strict' const express = require('express') const app = express() const http = require('http').createServer(app) const httpPort = '8080' const bodyParser = require('body-parser') // serve static files from a directory named public that is in the same // level as the server currently running, i.e. this file app.use(express.static('public')) // validate all user-controlled input before trusting it for further prorcessing app.use(bodyParser.urlencoded({ extended: true }) ) app.get('/api/healthcheck', function (req, res) { res.write('The server is running\n') res.end() }) // APIs and other business logic goes here // finally we start the listener process bound to the port // defined in the httpPort const at the top of this server file http.listen(httpPort, function () { console.log('HTTP server listening on port', httpPort) })
Database Connection
In order to take any CRUD - Create, Read, Update, Delete - actions in the database, I need an open connection to the database. This is done using the node mysql module.
Install it with npm, being sure to use the -S flag to save the mysql requirement to package.json.
npm install -S mysql2
If you run into any difficulties, take a look at the module’s NPM page here https://www.npmjs.com/package/mysql2
Once it’s installed, I add it to my list of module requirements at the top of my base-server.js file like this:
const mysql = require('mysql2')
Next, I add a block to provision a connection to the members database that I created earlier. I add this directly below my const at the top of the server. You can place it anywhere prior to the point in your server where you want to use the pool const to perform queries.
const pool = mysql.createPool({ host: 'localhost', user: 'databaseUser', password: 'secretPAssword', database: 'members', connectionLimit: 100, })
Registration Function
I’m going to place my two new APIs in the area that currently holds the APIs go here comment.
First, I remove that comment. I’ll use the healthcheck API as a starter for this work, so I’ll copy the healthcheck API code block into my clipboard & then insert it into that now empty space.
Next, I’ll make a quick edit to the API name and the HTTP method used. For a registration function, I will be passing in a few data elements - the new member’s username, email and password at a minimum - to the server. The POST method is well-suited to handle these elements as data in the body of the post. There is no strictly right or wrong method to use; if you had a preference to use GET and pass the incoming member data as query paramemters, you could do that.
The POST method is more aligned with a write action, so I use that for any APIs that write to my database.
Here’s what the registration function looks like so far:
app.post('/api/registration', function (req, res) { let action = req.body.action let account_id = req.body.account_id let email = req.body.email let account_type = req.body.account_type let member_name = req.body.member_name let password = req.body.password let role = req.body.role let salt = bcrypt.genSaltSync(10); // check if there's an action with the incoming request & whether it is a create action if ( action && action == 'create') { // validate that you have the important parameters. this checks for empty & undefined values. you can add other checks here but this covers the core if ((typeof member_name == 'undefined') || (member_name == '') || (member_name == null) || (typeof password == 'undefined') || (password == '') || (password == null) || (typeof email == 'undefined') || (email == '') || (email == null)) { // if there are errors, return the JSONified message and end the response return res.json({ error: true, message: 'Please supply appropriate parameters' }); } else { // since I store the user's password for later logins, I hash it first. This keeps the plaintext password from landing in my database let hash = bcrypt.hashSync(password, salt); // during development, I like to see what my workflow does. using this technique allows me to set the DEBUG flag as a const // at the top of the server once and then log messages to the console if the value is YES // you will want to only log important stuff in production and never log any plaintext credential values // the console in this case is the server's console, not the browser's console if (DEBUG == 'YES') { console.log('Hash: ', hash) } // setup the SQL statement using the incoming request's BODY parameters // these vars are set at the top of the registration function let create_sql = "INSERT INTO accounts (member_name, email, password, account_type, account_creation, account_modification_time, account_status, role) VALUES (\"" +member_name+ "\", \"" +email+ "\", \"" +hash+ "\", \"" +account_type+ "\", CURDATE(), CURDATE(), \"active\", \"" +role+ "\")"; pool.query(create_sql, function(err, row) { if (DEBUG == 'YES') { console.log('SQL:',create_sql) } if (err) { // do some errror handling let _Error = JSON.stringify(err); if (_Error.match(/Duplicate entry/g)) { return res.json({ error: true, message: 'Duplicate member name or email address. Please select another' }); } } else if ( typeof row === "undefined") { return res.json({ error: true, message: 'Unknown sql error' }); } else { // if there are no errors, return a success message // then end the response with res.end() if (DEBUG == 'YES') { console.log('account created') } res.write('Account created successfully') res.end() } }) } } })
Login Function
Now that the registration API is complete, a login API can be implemented. This is a simple login function with error checking.
app.get('/api/login', function(req, res) { let member_name = req.query.member_name; let password = req.query.password; if ( member_name && password ) { let login_sql = "SELECT * FROM accounts WHERE member_name = '" +member_name+ "'"; if (DEBUG == 'YES') { console.log('SQL used: ' + login_sql); } pool.query(login_sql, function(err, row) { if (err) { if (DEBUG == 'YES') { console.log(err);} res.status(500); } else if (!row.length) { return res.json({ success: false, message: 'Unknown account' }); } else { let account_id = row[0].account_id; let hashed_password = row[0].password; if (bcrypt.compareSync(password, hashed_password)) { res.write('Successful login') res.status(200).send; } else { res.json({success: false, message: 'There was a problem verifying your credentials'}); } } res.end(); }); } else { if (DEBUG == 'YES') { console.log("Request received but no parameters: " +req.body); } return res.json({ success: false, message: 'Please supply appropriate parameters.' }); } });
Once a member logs in successfully, I will set a token that can be used to provide access to services that map to their profile.
Testing
The registration API can be easily tested using curl.
I ensure my mysql server is running & then I start my node server. Refer to my previous app server post if you need a refresher on that.
Once the app server is running, I can run this curl command to register a new member to whatever service I’m building:
curl -X POST http://localhost:8080/api/registration -d "action=create&member_name=santa&email=jbminn@jbminn.com&password=f00Bar123&account_type=basic&role=developer"
This makes a POST to the registration API in my app server running on port 8080. It sends a POST body consisting of data elements that I passed to the -d parameter.
If there are no errors, I will see this message in my shell:
Account created successfully
I can also use curl to test the login. For example:
/Users/jbminn\>curl "http://localhost:8080/api/login?member_name=santa&password=f00Bar123" Successful login
Conclusion
This lesson showed how to implement registration & login functions that are exposed as APIs in a node.js app server. These are the foundations of any user-facing service that must authenticate & authorize members.