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
- ibm-verify-sdk - the sdk
- express - handling HTTP requests
- dotenv- for loading the Oauth configuration
- cookie-parser - handling cookies (optional)
- uuid - generating id (optional)
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/logout
to 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");
})