Express middleware that facilitates CAS and WSO2 integration with client side apps while also proving interoperability with legacy apps.


Keywords
middleware, byu, oauth, cas, jwt, brownie
License
Apache-2.0
Install
npm install byu-wabs@8.0.1

Documentation

byu-wabs

Brigham Young University's Web Application Bootstrap Service express middleware.

  • Manages WS02 OAuth Tokens
  • Keeps CAS user and WS02 user in sync
  • Interoperability with legacy applications (C-Framework)

Table of Contents

Examples

Express Server

const cookieParser      = require('cookie-parser');     // required by WABS middleware
const bodyParser        = require('body-parser');       // required by WABS middleware
const express           = require('express');
const path              = require('path');
const wabs              = require('byu-wabs');

const app = express();
const middleware = wabs('my-app');

// cookie parser needed for wabs authentication tools
app.use(middleware.ready(config => cookieParser(config.encryptSecret)));

// body parser needed for brownies
app.use(bodyParser.urlencoded({ extended: false, type: '*/x-www-form-urlencoded'}));
app.use(bodyParser.json());

// middleware for routes and adding req.wabs object, required for all other WABS middleware to function
app.use(middleware.init());

// create a protected endpoint
app.get('/protected', middleware.authenticated(), function(req, res) {
    res.send('Access granted');
});

// have CAS and WSO2 both identify the user prior to taking this path
app.get('/sync', middleware.sync(), function(req, res) {
    res.send('User is logged in: ' + !!req.wabs.user);
});

// login route (optional)
app.get('/login', middleware.login('/'));

// log out route (optional)
app.get('/logout', middleware.logout('/'));

// auth sync and html5 routing for paths that should resolve to the index file
app.use(middleware.html5Router({ indexPath: 'www/index.html' }));

// static file routing for static files
app.use(express.static(__dirname + '/www/'));

// error catching middleware
app.use(middleware.catch());

// start the server listening on the specified port
app.listen(3000, function(err) {
    if (err) console.error(err.stack);
    console.log('Listening on port 3000');
});

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>My App</title>
</head>
<body>
    <p>This is my app</p>
    <script src="/wabs/script.js"></script>     <!-- not required, just highly recommended -->
</body>
</html>

Configuration

The configuration is two part:

WSO2 Configuration

  1. Define a WSO2 application at https://api.byu.edu/store and then navigate to "My Applications"

  2. Create a new application.

  3. Set the application callback URL. The callback URL should point to your server at the path /wabs/oauth-code. If your developing locally on port 3000 then your callback URL would look like this: http://localhost:3000/wabs/oauth-code.

  4. Once the application is created navigate to "My Subscriptions"

  5. Select your app from the dropdown.

  6. Create some Sandbox or Production keys.

Middleware Configuration

When creating a WABS middleware instance you can provide it with a configuration. These are the options available:

  • appName - A unique identifier that will uniquely identify this server. If there are multiple instances of this server running in a load balanced situation, each instance should have the same appName.

  • awsConfig - A configuration to use when creating an instance of the AWS SSM. This is used to get a WABS configuration from the parameter store. Defaults to { region: 'us-west-2' }.

  • basePath - The path at which all WABS services will exist. Defaults to /wabs.

  • consumerKey - The WSO2 consumer key to use for authorization.

  • consumerSecret - The WSO2 consumer secret to use for authorization.

  • encryptSecret - A password used for encryption and decryption.

  • hardTimeout - The maximum number of minutes to allow authorization to persist from initial grant. Defaults to 600 which is equivalent to 10 hours.

  • host - The protocol, domain, and port used to reach this server. If omitted then auto detection will be used.

Middlewares

The wabs package is a collection of middlewares. The core piece of middleware, init, is required for all other WABS appendage middlewares to work.

const cookieParser      = require('cookie-parser');     // required by WABS middleware
const bodyParser        = require('body-parser');       // required by WABS middleware
const express           = require('express');
const wabs              = require('byu-wabs');

const app = express();
const middleware = wabs('my-app');

// cookie parser needed for wabs authentication tools
app.use(middleware.ready(config => cookieParser(config.encryptSecret)));

// body parser needed for brownies
app.use(bodyParser.urlencoded({ extended: false, type: '*/x-www-form-urlencoded'}));
app.use(bodyParser.json());

// required for all other WABS middleware to function
app.use(middleware.init());

Below are the additional middlewares and how to use them:

authenticated

This middleware checks to see if a local session has been established. The middleware must be initialized first.

Signature: wabs#authenticated ( [ options ] ) : Function

Parameters:

  • options - An object that specifies the behavior of the middleware. Options include the following properties:

    • startAuthentication - If set to true and the client does not have a local session then the client will be redirected to CAS and WSO2 for authentication and authorization. Note that if set to true that the HTTP method hitting this endpoint will be set to GET due to the nature of HTTP redirects. Defaults to false.

Returns a middleware function.

app.use(wabs.init());
app.use(wabs.authenticated());

catch

This middleware is best placed after all other middleware and is used to catch errors and to report them.

The error will be logged to the console with an ID and that ID is also sent to the client as a reference number so that the error can be looked up.

Signature: wabs#catch () : Function

Parameters: None

Returns: a middleware function.

app.use(wabs.catch());

clientGrantAccessToken

This middleware will get the client grant access token and store it on the request at req.wabs.clientGrantAccessToken.

Signature: wabs#clientGrantAccessToken () : Function

Parameters: None

Returns: a middleware function.

app.use(wabs.clientGrantAccessToken(), function(req, res, next) {
    console.log(req.wabs.clientGrantAccessToken);
    next();
});

html5Router

This middleware will return the index.html file for any route that does not have a file extension. It is best to include this middleware last (or at least very late) in relation to defined routes and other middleware.

Signature: wabs#html5Router () : Function

Parameters:

  • config - The router configuration options:

    • indexPath - A required option that specifies the path to the index.html file.

    • sync - If true then any requests to load the index.html file through the html5Router middleware will synchronize authentication and authorization prior to loading the index.html file. Defaults to true.

Returns: a middleware function.

app.use(wabs.html5Router({ indexPath: '/path/to/index.html' }));

login

This middleware initiates a login by obtaining authentication and authorization information through CAS and WSO2. This middleware can be used as a route.

Signature: wabs#login ([ redirect [, failure ] ]) : Function

Parameters:

  • redirect - The URL to redirect to on successful login. Defaults to the current URL, but that will cause an infinite redirect in many cases.

  • failure - The URL to redirect to on failure to log in. Defaults to using the same URL as the redirect parameter.

Returns: a middleware function.

app.get('/login', wabs.login('/'));

logout

This middleware causes the client to perform a logout. That includes deletion of sessions for WABS, CAS, WSO2, and the C-Framework. This middleware can be used as a route.

Signature: wabs#logout ([ redirect ]) : Function

Parameters:

  • redirect - The URL to redirect to on successful login. Defaults to the current URL, but that will cause an infinite redirect in many cases.

Returns: a middleware function.

app.get('/logout', wabs.logout('/'));

sync

This middleware performs a synchronization operation with CAS and WSO2 to make sure that everyone agrees on who is currently logged in. It is highly recommended that this middleware execute any time you are serving the index.html file to reduce cached session length (in case the user did log out but your app didn't get the memo).

This middleware is intended for use in GET routes.

Signature: wabs#sync () : Function

Parameters: None

Returns: a middleware function.

app.get('/', wabs.sync(), function(req, res) {
    if (req.wabs.user) {
        res.send('You are logged in as: ' + req.wabs.user.netId);
    } else {
        res.send('You are not logged in.');
    }
});

Server Request WABS Object

This object is generated with each request that is made. The object has multiple properties:

req.wabs.auth

This is an object that defines the currently authenticated user. It has the following properties:

  • accessToken - the access token associated with the active user.

  • refreshToken - the refresh token associated with the active user.

  • hardLimit - an ISO date string of when this user will no longer be able to refresh their access tokens.

  • user - a user object.

req.wabs.brownieDecode

A function that takes an encoded brownie and decodes it to to an object.

Signature: req.wabs.brownieDecode (encodedBrownie)

Parameters:

  • encodedBrownie - The brownie value to decode.

Returns: a promie that resolves to the decoded brownie.

req.wabs.brownieEncode

A function that takes a brownie object and encodes it to send to the C-Framework.

Signature: req.wabs.brownieEncode (data, seed)

Parameters:

  • data - The decoded brownie object.

  • seed - The seed needed to re-encode the brownie.

Returns: a promise that resolves to the decoded brownie.

req.wabs.getClientGrantAccessToken

Get the client grant access token.

Signature: req.wabs.getClientGrantAccesstoken ()

Parameters: none

Returns: a promise that resolves to the access token.

req.wabs.login

A function that when called will initiate a login sequence using redirects.

Signature: req.wabs.login ( gateway [, success [, failure ] ])

Parameters:

  • gateway - A boolean that signifies whether to use the CAS gateway. If true and the user is not logged in they will not be directed to the CAS login page.

  • success - The URL to direct the client to after successfully logging in. Defaults to the current request URL.

  • failure - The URL to direct the client to after failure to login. Defaults to the success URL.

Returns: undefined

req.wabs.logout

A function that when called will initiate a logout sequence using redirects.

Signature: req.wabs.logout ( redirect )

Parameters:

  • redirect - The URL to redirect the client to after logout. Note that currently (due to the C-Framework logout) the user will not automatically be redirected to this URL but will get a link that will direct to that URL.

Returns: undefined

req.wabs.refreshTokens

Manually refresh to code grant access tokens that are associated with the current user.

Signature: req.wabs.refreshTokens ()

Parameters: none

Returns: a promise that resolves to an HTTP status code. Before the promise resolves the information at req.wabs.auth will be updated with the new access tokens.

req.wabs.script

A function that when called respond to the client request with the wabs client-side JavaScript file.

Signature: req.wabs.script ()

Parameters: none

Returns: undefined

req.wabs.user

This is an object that defines the current user's personal information. It has the following properties which are all self explanatory:

  • byuId

  • netId

  • personId

  • preferredFirstName

  • prefix

  • restOfName

  • sortName

  • suffix

  • surname

  • surnamePosition

Client Application Tools

The client (a.k.a. the browser) can receive several helpful tools if you remember to include /wabs/script.js in your index.html file. (Note that if you change the basePath in the configuration options then this may be at a different path: for example: /<basePath>/script.js).

Here are the tools:

byu.auth.accessToken

Get the access token that represents the user. You'll need this access token to make direct API requests against WSO2. If the user is not logged in this value will be undefined.

const token = byu.auth.accessToken;

byu.auth.accessTokenExpires

Get a Date object for when the access tokens expires. After expiration it is likely still possible to refresh the tokens. If the user is not logged in this value will be undefined.

const date = byu.auth.accessTokenExpires;

byu.auth.expires

Get a Date object for when the WABS session expires. This value is calculated as 9 minutes after the access token expires or when the sessions duration hard limit is reached, whichever comes first. If the user is not logged in this value will be undefined.

const date = byu.auth.expires;

byu.auth.hardLimit

Get a Date object for when the WABS session can no longer be refreshed. If the user is not logged in this value will be undefined. If the client goes through the server's sync middleware then this hard limit will be updated.

const date = byu.auth.hardLimit;

byu.auth.login

Perform a login via JavaScript.

Signature: byu.auth.login ( [ redirect [, failure ] ] )

Parameters:

  • redirect - The URL to redirect to after login. If omitted then the current URL will be used.

  • failure - The URL to redirect to if the user fails to authenticate or authorize the application. Defaults to the redirect URL.

Returns: undefined

byu.auth.login();

byu.auth.logout

Perform a logout via JavaScript.

Signature: byu.auth.logout ( [ redirect ] )

Parameters:

  • redirect - The URL to redirect to after logout. If omitted then the current URL will be used.

Returns: undefined

byu.auth.logout();

byu.auth.refreshToken

Refresh the user access token.

You won't generally need to call this function because the user's access token will automatically be refreshed when it expires.

Signature: byu.auth.refreshToken ( [ callback ] )

Parameters:

  • callback - An optional function to call after the token has been refreshed.

Returns: undefined

byu.auth.refreshToken();

byu.brownie.clear

Remove all data from the brownie.

Signature: byu.auth.clear ( )

Parameters: none

Returns: undefined

byu.brownie.clear();

byu.brownie.get

Get the value for a specific brownie property.

Signature: byu.brownie.get ( [ property ] )

Parameters:

  • property - The name of the brownie property to get.

Returns: a number, a string, null, or undefined.

const id = byu.brownie.get('personId');

byu.brownie.list

Get an object with all brownie properties and values.

Signature: byu.brownie.list ()

Parameters: none

Returns: a number, a string, null, or undefined.

const obj = byu.brownie.list();

byu.brownie.navigateTo

Navigate to a new URL. If the URL is to the C-Framework then the brownie will be sent with the redirect.

This function does not need to be called unless the URL is to a C-Framework page. The brownie data will be maintained in the tab automatically.

Signature: byu.brownie.navigateTo ( url [, target ] )

Parameters:

  • url - The URL to navigate to.

  • target - The window target. Defaults to _self.

Returns: undefined

const id = byu.brownie.navigateTo('http://some-url.com');

byu.brownie.set

Set a brownie value.

Signature: byu.brownie.set ( property, value )

Parameters:

  • property - The name of the property to set.

  • value - The value to set. This must be a string, number, or null.

Returns: byu.brownie

byu.brownie.set('abc', 123);

byu.brownie.unset

Remove a brownie value.

Signature: byu.brownie.unset ( property )

Parameters:

  • property - The name of the property to remove.

Returns: byu.brownie

byu.brownie.unset('abc');

byu.user

Get the current user object. If the user is not authenticated and authorized then this value will be null, otherwise it will be an object with the following structure:

{
    byuId: '123456789',
    netId: 'abc123',
    personId: '123123123',
    preferredFirstName: 'Bob',
    prefix: '',
    restOfName: 'Bob E',
    sortName: 'Smith, Bob E',
    suffix: '',
    surname: 'Smith',
    surnamePosition: 'L'
}