Node.js: Accessing the user authentication REST API

The first step is to create a user data model for the Notes application. Rather than retrieving data from data files or a database, it will use REST to query the server we just created. Recall that we created this REST service in the theory of walling off the service since it contains sensitive user information.

Earlier, we suggested duplicating Chapter 7, Data Storage and Retrieval, code for Notes in the chap08/notes directory and creating the user information server as chap08/users.

Earlier in this chapter, we used the restify-clients module to access the REST service. That package is a companion to the Restify library; the restify package supports the server side of the REST protocol and restify-clients supports the client side.

However nice the restify-clients library is, it doesn’t support a Promise-oriented API, as is required to play well with async functions. Another library, SuperAgent, does support a Promise-oriented API and plays well in async functions, and there is a companion to that package, SuperTest, that’s useful in unit testing. We’ll use SuperTest in Chapter 13, Unit Testing and Functional Testing when we talk about unit testing.

To install the package (again, in the Notes application directory), run the following command:

$ npm install superagent@^5.2.x –save

Then, create a new file, models/users-superagent.mjs, containing the following code:

import { default as request } from ‘superagent’;

import util from ‘util’;

import url from ‘url’;

const URL = url.URL;

import DBG from ‘debug’;

const debug = DBG(‘notes:users-superagent’);

const error = DBG(‘notes:error-superagent’);

var authid = ‘them’;

var authcode = ‘D4ED43C0-8BD6-4FE2-B358-7C0E230D11EF’;

function reqURL(path) {

const requrl = new URL(process.env.USER_SERVICE_URL);

requrl.pathname = path;

return requrl.toString();

}

The reqURL function is similar in purpose to the connectDB functions that we wrote in earlier modules. Remember that we used connectDB in earlier modules to open a database connection that will be kept open for a long time. With SuperAgent, we don’t leave a connection open to the service. Instead, we open a new server connection on each request. For every request, we will formulate the request URL. The base URL, such as http://localhost:3333/, is to be provided in the USER_SERVICE_URL environment variable. The reqURL function modifies that URL, using the new Web Hypertext Application Technology Working Group (WHATWG) URL support in Node.js, to use a given URL path.

We also added the authentication ID and code required for the server. Obviously, when the backlog task comes up to use a better token authentication system, this will have to change.

To handle creating and updating user records, run the following code:

export async function create(username, password, provider, familyName, givenName, middleName, emails, photos) {

var res = await request

.post(reqURL(‘/create-user’))

.send({ username, password, provider,

familyName, givenName, middleName, emails, photos

})

.set(‘Content-Type’, ‘application/json’)

.set(‘Acccept’, ‘application/json’)

.auth(authid, authcode);

return res.body;

}

export async function update(username, password, provider, familyName, givenName, middleName, emails, photos) {

var res = await request

.post(reqURL(‘/update-user/${username}’))

.send({ username, password, provider,

familyName, givenName, middleName, emails, photos

})

.set(‘Content-Type’, ‘application/json’)

.set(‘Acccept’, ‘application/json’)

.auth(authid, authcode); return res.body;

}

These are our create and update functions. In each case, they take the data provided, construct an anonymous object, and POST it to the server. The function is to be provided with the values corresponding to the SQUser schema. It bundles the data provided in the send method, sets various parameters, and then sets up the Basic Auth token.

The SuperAgent library uses an API style called method chaining. The coder chains together method calls to construct a request. The chain of method calls can end in a .then or .end clause, either of which takes a callback function. But leave off both, and it will return a Promise, and, of course, Promises let us use this directly from an async function.

The res.body value at the end of each function contains the value returned by the REST server. All through this library, we’ll use the .auth clause to set up the required authentication key.

In this case, we’ve purposely chosen variable names for the parameters to match the field names of the object with parameter names used by the server. In doing so, we can use this shortened notation for anonymous objects, and our code is a little cleaner by using consistent variable names from beginning to end.

Now, add the following function to support the retrieval of user records:

export async function find(username) {

var res = await request

.get(reqURL(‘/find/${username}`))

.set(‘Content-Type’, ‘application/json’)

.set(‘Acccept’, ‘application/json’)

.auth(authid, authcode);

return res.body;

}

This is following the same pattern as before. The set methods are, of course, used for setting HTTP headers in the REST call. This means having at least a passing knowledge of the HTTP protocol.

The Content-Type header says the data sent to the server is in JavaScript Object Notation (JSON) format. The Accept header says that this REST client can handle JSON data. JSON is, of course, easiest for a JavaScript program—such as what we’re writing—to utilize.

Let’s now create the function for checking passwords, as follows:

export async function userPasswordCheck(username, password) {

var res = await request

.post(reqURL(‘/password-check’))

.send({ username, password })

.set(‘Content-Type’, ‘application/json’)

.set(‘Acccept’, ‘application/json’)

.auth(authid, authcode);

return res.body;

}

One point about this method is worth noting. It could have taken the parameters in the URL instead of the request body, as is done here. But since request URLs are routinely logged to files, putting the username and password parameters in the URL means user identity information would be logged to files and be part of activity reports. That would obviously be a very bad choice. Putting those parameters in the request body not only avoids that bad result but if an HTTPS connection to the service were used, the transaction would be encrypted.

Then, let’s create our find-or-create function, as follows:

export async function findOrCreate(profile) {

var res = await request

.post(reqURL(‘/find-or-create’))

.send({

username: profile.id, password: profile.password, provider: profile.provider,

familyName: profile.familyName, givenName: profile.givenName, middleName: profile.middleName,

emails: profile.emails, photos: profile.photos

})

.set(‘Content-Type’, ‘application/json’)

.set(‘Acccept’, ‘application/json’)

.auth(authid, authcode); return res.body;

}

The /find-or-create function either discovers the user in the database or creates a new user. The profile object will come from Passport, but take careful note of what we do with profile.id. The Passport documentation says it will provide the username in the profile.id field, but we want to store it as username instead.

Let’s now create a function to retrieve the list of users, as follows:

export async function listUsers() {

var res = await request

.get(reqURL(‘/list’))

.set(‘Content-Type’, ‘application/json’)

.set(‘Acccept’, ‘application/json’)

.auth(authid, authcode);

return res.body;

}

As before, this is very straightforward.

With this module, we can interface with the user information service, and we can now proceed with modifying the Notes user interface.

Source: Herron David (2020), Node.js Web Development: Server-side web development made easy with Node 14 using practical examples, Packt Publishing.

Leave a Reply

Your email address will not be published. Required fields are marked *