Skip to main content

Quickstart (Node.js)

The following page will guide you through creating a server based on express. The server will allow a user to authenticate through SV, logout and perform requests on behalf of the user - specifically retrieving the list of registered authenticators.

NOTE: for the sake of simplicity, an array is being used for token storage. Typically you would use a database.

The full source code is available at the bottom of the page.

Initialise the package

npm init -y

The -y flag uses the default configuration

Dependencies

npm install ibm-verify-sdk express dotenv cookie-parser uuid

.env

The recommended way of storing the OAuth configuration is in a file named .env this way the values are not hard coded into the source. Using the node package dotenv we can load the contents of the file into the application environment variables.

An example configuration.

TENANT_URL=https://xxxxxxx.ibmcloud.com
CLIENT_ID=xxxx
CLIENT_SECRET=xxxx
REDIRECT_URI=http://localhost:3000/authorize/callback
RESPONSE_TYPE=code
FLOW_TYPE=authorization
SCOPE=openid
REGISTRATION_PROFILE_ID=xxxx

index.js

Create the file index.js this will be the server

Imports

const express              = require('express');
const cookieParser         = require('cookie-parser'); // optional
const uuidv1               = require('uuid/v1');       // optional
const OAuthContext         = require('ibm-verify-sdk').OAuthContext;
const AuthenticatorContext = require('ibm-verify-sdk').AuthenticatorContext;

express setup

const app = express();
app.use(cookieParser('secret')); // optional

Load config

// load contents of .env into process.env
require('dotenv').load();

let config = {
    tenantUrl    : process.env.TENANT_URL,
    clientId     : process.env.CLIENT_ID,
    clientSecret : process.env.CLIENT_SECRET,
    redirectUri  : process.env.REDIRECT_URI,
    responseType : process.env.RESPONSE_TYPE,
    flowType     : process.env.FLOW_TYPE,
    scope        : process.env.SCOPE
};

Instantiate OAuthContext and AuthenticatorContext

let authClient = new OAuthContext(config);
let authCtx    = new AuthenticatorContext(authClient);

Token storage

The element is recorded in the array as{id, token}.

let usersToToken = [];

login route

// generate authentication url and redirect user
app.get('/login', (req, res) => {
    authClient.authenticate().then(url => {
        res.redirect(url);
    }).catch(error => {
        res.send(error);
    })
})

callback route

After authenticating through the tenant the user will be redirected to this route

// user has authenticated through SV, now get the token
app.get('/authorize/callback', (req, res) => {
    authClient.getToken(req.url).then(token => {

        // generate id
        let id = uuidv1();

        // store id in signed cookie - expiry not working
        res.cookie('uuid', id, {signed: true});

        // store and associate token to the user
        usersToToken.push({id: id, token: token});

        // redirect to authenticated page
        res.redirect('/home');

    }).catch(error => {
        res.send("ERROR: " + error);
    });
});

logout route

// delete token from storage
app.get('/logout', (req, res) => {
    // get id from cookie
    let id = req.signedCookies.uuid;

    if (!id) {
        res.send("Cannot find cookie");
        return;
    }

    // remove record
    for (let i = 0; i < usersToToken.length; i ++) {
        if (usersToToken[i].id === id) {
            usersToToken.splice(i);
            res.send("Logged out");
            return;
        }
    }

    res.send("User not found");
})

authenticators route

All the other functions in AuthenticatorContext follow the same pattern.

// returns an array of the users registered authenticators
app.get('/api/authenticators', (req, res) => {
    // get id from cookie
    let id = req.signedCookies.uuid;

    // retrieve token
    let token;

    for (let i = 0; i < usersToToken.length; i ++) {
        if (usersToToken[i].id === id) {
            token = usersToToken[i].token;
            break;
        }
    }

    // if token was found in storage
    if (token) {
        // set correct header
        res.setHeader("Content-Type", "application/json");

        authCtx.authenticators(token).then(response => {

            res.send(JSON.stringify(response.response));

            // refresh occurred
            if (response.token) {
                console.log("Refreshed token");

                // update record
                for (let i = 0; i < usersToToken.length; i ++) {
                    if (usersToToken[i].id === id) {
                        usersToToken[i].token = response.token;
                        break;
                    }
                }
            }
        }).catch(error => {
            res.send(JSON.stringify(error));
        })
    } else {
        res.send("Token not found");
    }
})

Start express

app.listen(process.env.port || 3000, () => {
    console.log("Server started");
})

Summary

Start the server node index.js

navigate to http://localhost:3000/login login through the SV tenant

now navigate to http://localhost:3000/authenticators to view your registered authenticators

finally http://localhost:3000/logoutto logout.

NOTE: If refresh token functionality is not working ensure that Generate refresh tokens is enabled under application settings in SV - see troubleshooting for more information.

Full source

click here for the full source code
const express              = require('express');
const cookieParser         = require('cookie-parser'); // optional
const uuidv1               = require('uuid/v1');       // optional
const OAuthContext         = require('ibm-verify-sdk').OAuthContext;
const AuthenticatorContext = require('ibm-verify-sdk').AuthenticatorContext;



const app = express();

app.use(cookieParser('secret')); // optional

// load contents of .env into process.env
require('dotenv').load();

let config = {
    tenantUrl    : process.env.TENANT_URL,
    clientId     : process.env.CLIENT_ID,
    clientSecret : process.env.CLIENT_SECRET,
    redirectUri  : process.env.REDIRECT_URI,
    responseType : process.env.RESPONSE_TYPE,
    flowType     : process.env.FLOW_TYPE,
    scope        : process.env.SCOPE
};

let authClient = new OAuthContext(config);
let authCtx    = new AuthenticatorContext(authClient);

// storage for user tokens - use a database!
let usersToToken = [];

// generate authentication url and redirect user
app.get('/login', (req, res) => {
    authClient.authenticate().then(url => {
        res.redirect(url);
    }).catch(error => {
        res.send(error);
    })
})

// user has authenticated through SV, now get the token
app.get('/authorize/callback', (req, res) => {
    authClient.getToken(req.url).then(token => {

        // generate id
        let id = uuidv1();

        // store id in signed cookie - expiry not working
        res.cookie('uuid', id, {signed: true});

        // store and associate token to the user
        usersToToken.push({id: id, token: token});

        // redirect to authenticated page
        res.redirect('/home');

    }).catch(error => {
        res.send("ERROR: " + error);
    });
});

// delete token from storage
app.get('/logout', (req, res) => {
    // get id from cookie
    let id = req.signedCookies.uuid;

    if (!id) {
        res.send("Cannot find cookie");
        return;
    }

    // remove record
    for (let i = 0; i < usersToToken.length; i ++) {
        if (usersToToken[i].id === id) {
            usersToToken.splice(i);
            res.send("Logged out");
            return;
        }
    }

    res.send("User not found");
})

// returns an array of the users registered authenticators
app.get('/api/authenticators', (req, res) => {
    // get id from cookie
    let id = req.signedCookies.uuid;

    // retrieve token
    let token;

    for (let i = 0; i < usersToToken.length; i ++) {
        if (usersToToken[i].id === id) {
            token = usersToToken[i].token;
            break;
        }
    }

    // if token was found in storage
    if (token) {
        // set correct header
        res.setHeader("Content-Type", "application/json");

        authCtx.authenticators(token).then(response => {

            res.send(JSON.stringify(response.response));

            // refresh occurred
            if (response.token) {
                console.log("Refreshed token");

                // update record
                for (let i = 0; i < usersToToken.length; i ++) {
                    if (usersToToken[i].id === id) {
                        usersToToken[i].token = response.token;
                        break;
                    }
                }
            }
        }).catch(error => {
            res.send(JSON.stringify(error));
        })
    } else {
        res.send("Token not found");
    }
})

app.listen(process.env.port || 3000, () => {
    console.log("Server started");
})