byu-wabs
TypeScript icon, indicating that this package has built-in type declarations

8.0.1 • Public • Published

byu-wabs

Table of Contents

About WABS

The Web Application Bootstrap Service (WABS) is a NodeJS Express middleware that provides a useful set of common tools, such as:

  • CAS Authentication
  • WSO2 Authorization
  • Interoperability with Legacy Applications
  • Integrates with byu-browser-oauth

CAS Authentication

WABS uses CAS to make sure that the user is correctly signed in when they should be and signed out when they should be.

BYU's Central Authentication System (CAS) is used to establish user identity. CAS offers single sign-on, so if a user signs into one application that uses CAS authentication then goes to another application that uses CAS authentication then they could already be signed into the second application. CAS also offers single sign-out, causing a sign out of CAS in one application to sign out everyone.

Unfortunately not all of BYU's web applications properly support CAS's single sign-on, sometimes requiring the user to click the "Sign In" button even if they are already signed in. Additionally, no BYU web applications currently support single sign-out.

WSO2 Authorization

BYU uses OAuth 2 and WSO2 to allow your application and it's users to access internal BYU web services. WABS provides tools to simplify the WSO2 authorization process. Additionally WABS will automatically manage OAuth tokens, refreshing tokens when necessary.

A problem that WABS solves with WSO2 is the authenticated user synchronization issue. A times WSO2 may believe erroneously that the signed in user is one person when it is actually another. (This issue can occur if two people are using the same computer.) WABS fixes this issue by making sure that the user's CAS identity matches their WSO2 identity and forcing an update in the case where they do not match.

Legacy Applications

Many of BYU's legacy applications require state to be maintained on a per tab (not per browser) basis. This data was stored into what became known at BYU as a brownie. Because there was no way to maintain tab specific data prior to browser's session storage, BYU used HTML forms to POST the tab specific information from one page to the next.

WABS will automatically receive brownies and send brownies in a way that is compliant with legacy applications. Additionally it uses session storage to store data per tab, automatically determining when that data is no-longer valid, and clearing it.

Table of Contents

Getting Started

To run your application using WABS you first need to set a few things up:

  1. Create a WSO2 Application
  2. Create a WABS Configuration
  3. Add WABS Middleware to Your Server

Create a WSO2 Application

You may need to get access to create a WSO2 application. Contact OIT if this is the case.

  1. Visit https://api.byu.edu/store.
  2. Select the link My Applications.
  3. Fill out the form to add a new application. The callbackURL should be set to your domain name. For example, if you are developing locally on port 3000 then the callbackUrl should be http://localhost:3000/.
  4. Save the WSO2 application.
  5. Select the link My Subscriptions.
  6. From the drop down menu select your newly created application.
  7. Click the button to generate keys for your application.
  8. You will need your Consumer Key and Consumer Secret values for the WABS configuration.

Create a WABS Configuration

If you plan to use authentication / authorization then you have two options:

  1. Recommended option: Store the configuration in the AWS parameter store.

  2. Easy option: Create a JSON file outside of your project directory that contains the protected configuration properties.

These are all of the options you can use with a WABS configuration:

Option Type Description Default
appName String [ REQUIRED ] The name of the application. This value must be unique to your application. This can consist of letter, numbers, underscore and dash.
awsParameterName String The name of the AWS parameter store parameter to read additional configuration options from. Default AWS Parameter Name
awsSsmConfig Object A configuration to pass to the AWS SDK SSM constructor. { region: 'us-west-2' }
casCallbackPath String The path to redirect the browser to after CAS authentication. This value should match the path that your WABS init middleware is running from. /
configFile String A file path to read additional configuration options from.
consumerKey String The WSO2 consumer key. 2
consumerSecret String The WSO2 consumer secret. 2
encryptSecret String A seed to use to encrypt your applications sensitive information. This must be a string of at least 40 characters in length. 2
hardTimeout Number The maximum number of minutes to allow someone to be logged into your application. 600
host String The protocol, domain, and port used to get to your server. For example: http://localhost:3000. Generally this value can be determined automatically, but if you are having issues when people try to log in then you can try to set the host manually.
apiHost String The hostname of the API Manager used for authentication.
openProxy Boolean Whether to allow the web browser to indiscriminately proxy web service requests through the server, using the server's identity to communicate with web serves. false
reservedPath String A reserved path that the init middleware uses. "/__wabs"
wso2CallbackPath String The path to redirect the browser to after WSO2 authorization. This value must match the path (excluding protocol, domain, and port) that you specified when creating your WSO2 application. /
Default AWS Parameter Name

The default value for awsParameterName is derived from a combination of the appName, the environment (defined by the environment variables NODE_ENV or HANDEL_ENVIRONMENT_NAME, defaulting to "dev"), and the suffix "WABS_CONFIG" joined by dots (.). For example if your app is named "my-app" and no environment is specified then this value will default to "my-app.dev.WABS_CONFIG".

Protected Configuration Options

The consumer key, consumer secret, and encrypt secret are all required if your application uses authentication / authorization. Mechanisms are in place to prevent you from putting your consumer key, consumer secret, and encrypt secret in unsafe locations. NEVER STORE YOUR WSO2 CLIENT ID, WSO2 CLIENT SECRET, OR ENCRYPT SECRET IN YOUR CODE OR WITHIN YOUR APPLICATION DIRECTORY.

Where to Put the Configuration

If you plan to use authentication and authorization with your application then the consumerKey, consumerSecret, and encryptSecret cannot be stored within your code or within a configuration file that resides within in your project directory. You can store the consumerKey, consumerSecret, and encryptSecret in a configuration file outside of your project directory or in an AWS Parameter store.

In Your Code

Within your code you can specify the appName, awsConfig, awsParameterName, casCallbackPath, configFile, hardTimeout, host, and wso2CallbackPath. This excludes only the consumerKey, consumerSecret, and encryptSecret which, if you attempt to include, will throw an error due to security concerns.

Define with App Name Only
const Wabs = require('byu-wabs');
const middleware = Wabs('my-app-name'); // shortcut for defining just the app name
Define with Configuration File Path

The file path must include a path delimiter to be recognized as a path. This path cannot exist within your application directory or an error will be thrown.

const Wabs = require('byu-wabs');
const middleware = Wabs('/path/to/config.json');
Define with Configuration
const Wabs = require('byu-wabs');
const middleware = Wabs({
    appName: 'my-app-name',
    awsParameterName: 'my-app-param-name',
    awsSsmConfig: { region: 'us-west-2' },
    casCallbackPath: '/',
    configFile: '/path/to/config.json',
    hardTimeout: 600,
    host: 'http://localhost:3000',
    wso2CallbackPath: '/'
});
In the AWS Parameter Store

This is the recommended method for storing your configuration. By keeping this configuration in a central location other developers can also work on your code without having to acquire the secure configuration of your app.

The name of this parameter can be specified in the middleware configuration using the awsParameterName option. Alternatively it will use the default name.

Create a parameter in the AWS Parameter Store that has a value that is a JSON string containing all configuration options you'd like to store on AWS. Tell your configuration in code to use that parameter name.

server.js
const Wabs = require('byu-wabs');
const middleware = Wabs('my-app-name');
parameter store

Assuming that the environment has not been set, because the awsParameterName has not been set, the default will be used with is derived to "my-app-name.dev.WABS_CONFIG"1

my-app-name.dev.WABS_CONFIG = { "consumerKey": "<consumer-key>", "consumerSecret": "<consumer-secret>", "encryptSecret": "<encrypt-secret>" }
In a Configuration File

Due to security concerns, the configuration file must reside outside of your project directory. Any configuration option can be specified in this file but may be overwritten by configuration settings made in the application code. In other words, if you specify an option here that is already specified in the application code then the application code will take precedence.

server.js
const Wabs = require('byu-wabs');
const middleware = Wabs('/path/to/config.json');
/path/to/config.json
{
    "appName": "my-app-name",
    "awsParameterName": "my-app-param-name",
    "awsSsmConfig": { "region": "us-west-2" },
    "casCallbackPath": "/",
    "consumerKey": "<consumer-key>",
    "consumerSecret": "<consumer-secret>",
    "encryptSecret": "<encrypt-secret>",
    "hardTimeout": "600",
    "host": "http://localhost:3000",
    "wso2CallbackPath": "/"
}

Table of Contents

Add WABS Middleware to Your Server

You'll need to be familiar with Express Middleware to understand this section.

WABS is a middleware that you can use to add functionality to your server. That functionality includes the mechanisms for authentication, authorization, token management, and interoperability with legacy code.

// get dependencies
const cookieParser  = require('cookie-parser');
const express       = require('express');
const path          = require('path');
const wabs          = require('byu-wabs')('my-app-name');

// create the express app
const app = express();

// must parse cookies before wabs.init
app.use(cookieParser(wabs.config.encryptSecret));
app.use(wabs.init());

// serve index.html and static files
const publicDirectoryPath = '/path/to/app/public';
app.use(wabs.index({ render: publicDirectoryPath + '/index.html' }));
app.use(express.static(publicDirectoryPath))

// start listening for requests
app.listen(3000, function(err) {
    if (err) return console.error(err.stack);
    console.log('Server listening on port: 3000');
});

At this point you are ready to start your application and open a browser.

Table of Contents

Server Middleware

This section covers the middlewares bundled with WABS and how to use them. It does not cover details about configuring the WABS instance. For that information check out the Getting Started section.

Middleware Table of Contents
  • authenticated - An mid-route middleware for ensuring the user is authenticated.
  • brownie - Run brownie receiving and parsing middleware.
  • clientGrantProxy - Middleware for proxying client requests.
  • index - Middleware for keeping authentication in sync and loading the index.html file.
  • init - A required initialization middleware.
  • login - A middleware for creating a login endpoint.
  • logout - A middleware for creating a logout endpoint.
  • sync - A middleware for synchronizing the current authentication / authorization state.

authenticated

The authenticated middleware can be run mid route to ensure that a user is either authenticated or forbidden from accessing the route. Useful if you don't want a user to to access a protected route without being authenticated.

Parameters

  • options - A configuration object.

    • authenticate - Whether to redirect the user to authenticate if they are not logged in. This can only occur for GET requests. Defaults to false.

Returns a middleware function.

app.get('/protected', wabs.authenticated(), function(req, res) {
    res.send('Protected data');
});

Table of Contents | Middleware Table of Contents

brownie

This middleware provides legacy application interoperability where brownies are in use by listening for POST requests. If a POST is received then this middleware will read the body and attempt to decode it as a brownie payload.

Parameters

None

Returns a middleware function.

app.use(wabs.brownie());

Table of Contents | Middleware Table of Contents

clientGrantProxy

The clientGrantProxy middleware can be used to proxy requests from the browser and use OAuth 2's client grant credentials to access a web service on another server. Note that client grant credentials are tied to your application and do not contain any information about the user, therefore it is best to use this as a proxy for endpoints where the user does not need to be authenticated.

Parameters

  • requestOptions - A Function or a String.

    If a Function then the function will be called with the current request object as it's parameter. The function should return an object that will be passed to the request (v2) module.

    If a String then this value will be used as the base URL for the request and the rest of the request will be forwarded to proxied endpoint.

Returns a middleware function.

In the following examples a request to "/my-proxy-path/abc/123" for this endpoint would proxy to "https://api.byu.edu/my-endpoint/abc/123":

app.use('/my-proxy-path', wabs.clientGrantProxy('https://api.byu.edu/my-endpoint'));
app.use('/my-proxy-path', wabs.clientGrantProxy(function(req) {
    return {
        baseUrl: 'https://api.byu.edu/my-endpoint',
        body: req.body || '',
        headers: Object.assign({}, req.headers),
        method: req.method,
        qs: Object.assign({}, req.query),
        url: req.path
    }
});

Table of Contents | Middleware Table of Contents

index

This middleware is used to produce the index.html file, to receive brownies, and to synchronize authentication / authorization.

Parameters

  • options - A configuration object.

    • brownie - A Boolean that is set to true to allow reception and decoding of brownies. Defaults to true.

    • injectScript - A Boolean that if set to true will inject the <script> tag into your index.html file that will cause the WABS client script to be downloaded.

    • ignore - A array of strings or regular expressions (Array.<String|RegExp>) or a Function.

      If Array.<String|RegExp> then each item in the array will be compared to the request path and if found to be a match will cause the index.html not to be returned.

      If a Function then the function will receive the current path and should return true if the path should not return the index.html.

    • render - REQUIRED. A String or a Function.

      If a String then this should be the file path to your index.html file.

      If a Function then the function will be called with two parameters: 1) request and 2) a callback function. The callback function should be called with two parameters: 1) the error (if any), 2) the final HTML content to send as your index.html content.

    • sync - A Boolean that if set to true will get the latest authentication / authorization before loading the index.html content. This behavior is recommended if you're building a single page app.

Returns a middleware function.

app.use(wabs.index({ render: '/path/to/index.html' });
app.use(wabs.index({
    render: function(req, callback) {
        const content = '<html><body>Hello, World!</body></html>';
        callback(null, content);
    }
});

Table of Contents | Middleware Table of Contents

init

This middleware must execute before any other wabs middleware.

Parameters: None

Returns a middleware function.

app.use(wabs.init());

Table of Contents | Middleware Table of Contents

login

This middleware can be used to define a login endpoint. This middleware is not necessary if you only plan to use the client side script wabs.auth.login().

Parameters

  • options - An optional configuration object.

    • failure - A URL to redirect to if the login fails. This can be overwritten if the incoming request has a failure query parameter. Defaults to success value.

    • gateway - Whether to use the CAS gateway. Using the gateway will check to see if the user is logged in but will not display the login form if they are not logged in. This can be overwritten if the incoming request has a gateway query parameter set to "true" or "false". Defaults to false.

    • query - An optional object for adding additional query parameters to the CAS login URL.

    • success - A URL to redirect to if the login success. This can be overwritten if the incoming request has a success query parameter. Defaults to "/".

    • wso2 - Whether the browser should be redirected to WSO2 after successful CAS authentication. Defaults to true.

Returns a middleware function.

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

Table of Contents | Middleware Table of Contents

logout

This middleware can be used to define a logout endpoint. This middleware is not necessary if you only plan to use the client side script wabs.auth.logout().

Parameters

  • options - An optional configuration object.

    • cas - Set to true to have this middleware also log out of CAS. Defaults to true.

    • cFramework - Set to true to have this middleware also log out of the C-Framework. Defaults to true.

    • redirect - The URL to redirect to after logout. This value can be overwritten if the incoming request has a redirect query parameter. Defaults to "/".

    • wso2 - Set to true to have this middleware also log out of WSO2. Defaults to true.

Returns a middleware function.

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

Table of Contents | Middleware Table of Contents

sync

Middleware that will transparently redirect the request through CAS and WSO2 to get the latest authentication and authorization data.

Parameters None

Returns a middleware function.

In this example when the browser navigates to "/some/path" it will be redirected to CAS and WSO2 prior to calling the routerHandler function. This will help ensure that the current user's authentication and authorization is up to date.

app.get('/some/path', wabs.sync(), function routeHandler(req, res) {
    // ...
});

Table of Contents | Middleware Table of Contents

Client Script

The WABS client script is automatically added to your index file (immediately before your first script tag) when requested by a browser. This script file will finish executing prior to the execution of any of your scripts. If you have set either the NODE_ENV or HANDEL_EVIRONMENT_NAME to a value other than "dev" then this script will be minified. It provides the following functionality:

  • Automatic synchronization of authenticated status between other WABS applications.
  • Automatically refreshes the authenticated user's OAuth access token before expiration.
  • Provides functionality to manually manage authentication and authorization.
  • Easy access to get or set the brownie.
  • Automatic re-encoding of the brownie.
  • Automatic and manual sending of the brownie.
  • Fires events for important changes in state.
Client Script Table of Contents
  • Events - Subscribe to events to keep your app authorization / authentication synchronized.
  • byu.ajax - A small AJAX helper function
  • byu.auth.accessToken - A getter for the current access token.
  • byu.auth.expires - A getter for the current access token expiration date.
  • byu.auth.login - A function to initiate a login.
  • byu.auth.logout - A function to initate a logout.
  • byu.auth.proxy - A function to make proxied AJAX requests that will use the WSO2 client grant token for authorization.
  • byu.auth.refreshToken - A function to refresh the access token.
  • byu.auth.request - A wrapper around byu.ajax that adds the code grant access token to the request and automatically retries if the token has expired.
  • byu.auth.sync - A function to perform a authorization / authentication synchronization.
  • byu.navigateTo - A function to POST brownie data to another page.
  • byu.user - The currently logged in user.

Events

WABS will emit events that can be captured at the document level:

<script>
    document.addEventListener('wabs-auth-login', function(event) {
        console.log('User logged in: ' + byu.user.netId);
    });
</script>

These are the emitted events:

  • wabs - This event will be emitted immediately after any other events. It can be used to capture all WABS events. The event detail property will contain the name of the original event as well as its data.

    <script>
        document.addEventListener('wabs', function(event) {
            console.log('Event name: ' + event.detail.name);
            console.log('Event data: ' + event.detail.data);
        });
    </script>
  • wabs-auth-login - Ths event is emitted when a user is authenticated. The access token may or may not be available at this point, but the event detail will tell whether or not it is gettingAccessToken. If set to false you may want to call byu.auth.login() which will begin the process of getting the accessToken.

    <script>
        document.addEventListener('wabs-auth-login', function(event) {
            if (!event.detail.gettingAccessToken) byu.auth.login();
        });
    </script>
  • wabs-auth-logout - This even is emitted when the user loses authentication.

    <script>
        document.addEventListener('wabs-auth-logout', function() {
            console.log('User logged out');
        });
    </script>
  • wabs-access-token-update - This event is emitted when the access token changes. That could mean there is a new access token or none at all.

    <script>
        document.addEventListener('wabs-access-token-update', function() {
            console.log('New access token: ' + byu.auth.accessToken);
        });
    </script>

Table of Contents | Client Script Table of Contents

byu.ajax

A simple AJAX wrapper. If you're not using IE11 then just use JavaScript fetch. Also, this function a light AJAX implementation and is not intended to solve every use case, although it will work for most.

Signature byu.ajax(options [, callback ])

Parameters

  • options - A string or an Object the defines the request. If a string then the value will be used as the request URL and all other default values will be kept. If an Object then the following properties can be set.

    • body - A string or an Object. If an Object then the value will automatically be serialized for transmission and the headers for Content-Type will automatically be set to application/json.

    • headers - An Object.<string,string> with name value pairs where the name represents the header name and the value is the header value. All header names will automatically be converted to lower case.

    • method - The HTTP method to use. Defaults to "GET".

    • url - The URL to send the request to. This value is required.

  • callback - An optional function to call once the request has completed. This function will receive the parameters: 1) body and 2) statusCode.

Returns The XMLHttpRequest instance that sent the request if the callback is provided or a promise if the callback parameter was omitted. The promise resolves to an object with the properties body and status.

byu.ajax('http://someurl.com/', function(body, code) { ... });

byu.ajax({
    method: 'POST',
    url: 'http://someurl.com',
    headers: {
        'x-header': 'value'
    },
    body: {
        propertyName: 'propertyValue'
    }
}, function(body, code) { ... });

Table of Contents | Client Script Table of Contents

byu.auth.accessToken

Get the WSO2 access token for the authenticated user. Just because a user is authenticated doesn't mean that they also have an access token although most often they will.

Table of Contents | Client Script Table of Contents

byu.auth.expires

Get the date that the WSO2 access token expires.

Table of Contents | Client Script Table of Contents

byu.auth.login

Initialize a login sequence by redirecting the browser through the CAS login screen and the WSO2 authorization page.

Signature byu.auth.login([ success ,] options ])

Parameters

  • success -The URL to navigate to after successful login. This parameter can be omitted while still providing the options parameter. Defaults to the current page URL.

  • options - A String or an Object. If a String then this represents the URL to navigate to if authentication fails. If an Object then it has the following properties:

    • failure - The URL to navigate to if authentication fails. Defaults to the success URL value.

    • popup - Whether to use a popup window for login. Defaults to false.

Returns undefined

Example

<button onclick="byu.auth.login()">Sign In</button>

Table of Contents | Client Script Table of Contents

byu.auth.logout

Initialize a logout sequence. This attempts to be a single sign-out as much as possible by logging out of CAS, WSO2, cFramework applications, and other WABS applications.

Signature byu.auth.logout([[ redirect ,] options ])

Parameters

  • redirect - The URL to navigate to after logout. This parameter can be omitted while still providing the options parameter. Defaults to the current page URL.

  • options - An object that specifies which services to log out of.

    • cas - Log out of CAS. Defaults to true.

    • cFramework - Log out of the cFramework. Defaults to true.

    • popup - Cause the log out to happen via a popup window (will not allow logging out of the cFramework). Defaults to false.

    • wso2 - Log out of WSO2. Defaults to true.

Returns undefined

Example

<button onclick="byu.auth.logout()">Sign Out</button>

Table of Contents | Client Script Table of Contents

byu.auth.proxy

Make an AJAX request that is proxied by the server. The server will add its own access token to the request. Note that calling this function will not work unless the option openProxy on the constructor is set to true.

Signature byu.auth.proxy( options [, callback ])

Parameters

  • options - A String or an Object. If a String then the request will use the value as the URL and all other options will be default values.

    • body - The body to send with the request.

    • method - The http method to use. Defaults to "GET".

    • headers - An Object with name value pairs.

    • url - REQUIRED The URL to proxy against.

  • callback - A callback to call when the request completes. The callback receives the response body and status code.

Example

byu.auth.proxy('https://api.byu.edu/some/api', function(body, status) {
    console.log(body, status);
});

Table of Contents | Client Script Table of Contents

byu.auth.refreshToken

Manually refresh the WSO2 access token.

Signature byu.auth.refreshToken([ callback ])

Parameters

  • callback - The function to call once the refresh request has completed.

Returns undefined

Example

byu.auth.refreshToken(function(body, status) {
    console.log(body, status, byu.auth.accessToken);
});

Table of Contents | Client Script Table of Contents

byu.auth.request

Make an AJAX request that automatically adds the user's access token to the authorization header. The user MUST be logged in for this function to work. If the response for the request indicates an invalid access token then the access token is automatically refreshed and one more attempt is made.

Signature byu.auth.request( options [, callback ])

Parameters

  • options - A String or an Object. If a String then the request will use the value as the URL and all other options will be default values.

    • body - The body to send with the request.

    • method - The http method to use. Defaults to "GET".

    • headers - An Object with name value pairs.

    • url - REQUIRED The URL to proxy against.

  • callback - An optional callback to call when the request completes. The callback receives the response body and status code. If omitted then a promise will be returned.

Returns A promise if the callback parameter was omitted. The promise resolves to an object with the properties body and status.

Example

byu.auth.request('https://api.byu.edu/some/api', function(body, status) {
    console.log(body, status);
});

Table of Contents | Client Script Table of Contents

byu.auth.sync

Synchronize the current authentication and authorization state. This will cause a popup window to appear briefly that will check with CAS for current authentication and will go through WSO2 to acquire the latest access token.

This function will be called automatically if any other WABS application signs in our out.

Signature byu.auth.sync([ callback ])

Parameters

  • callback - If provided, the callback will be called after synchronization has completed and it will receive two paramters: 1) the current user, 2) the current access token.

Returns undefined

byu.auth.request('https://api.byu.edu/some/api', function(user, accessToken) {
    if (user) {
        console.log('User logged in: ' + user.netId);
    } else {
        console.log('User not logged in.');
    }
});

Table of Contents | Client Script Table of Contents

byu.navigateTo

Navigate while posting current brownie data to the receiving URL. Note that any links (<a>) with a target of _blank or that go to a C-Framework application will automatically call this function.

Signature byu.navigateTo( url [, target [, callback ] ])

Parameters

  • url - The URL to navigate to.

  • target - The window target to cause to navigate. Defaults to _self.

  • callback - A function to call immediately prior to navigation. If an error occurs then this function will receive that error as its first parameter.

Returns undefined

byu.navigateTo('https://myapp.byu.edu');

Table of Contents | Client Script Table of Contents

byu.user

Get the currently authenticated user. If not authenticated then this value will be null.

console.log(byu.user);

Table of Contents | Client Script Table of Contents

Package Sidebar

Install

npm i byu-wabs

Weekly Downloads

91

Version

8.0.1

License

Apache-2.0

Unpacked Size

146 kB

Total Files

15

Last publish

Collaborators

  • mjweather
  • yoshutch
  • lehinpm
  • oscea
  • stuft2
  • mhailstone
  • arasmus8
  • garygsc
  • martingarn
  • snelg
  • byu-oit-bot
  • gi60s