I recently wrote about unit testing Cloudfront Functions which presented some challenges. This post is about unit testing Auth0 Action Scripts when using a custom database, which also presented some similar challenges but had a different solution.

What is Auth0?

Auth0 is an Identity-as-a-service (IaaS) provider.

What is a custom database and when would I use one?

When using Auth0 there is the facility to create one or more user databases that store users authenticating with a username/password combination (link). Should you need to, these stores can be connected to an external database that (presumably) you already control. A custom database is a database not controlled by Auth0, likely a SQL database but it doesn’t actually matter - it doesn’t really have to be a database at all, just return users.

The most likely use for a custom database is probably going to be lazily migrating users from an old identity provider to Auth0. Lazy migration will allow users to be moved over to Auth0 at the point of login without needing to change their password (link). Another use for a custom database is to not use Auth0 for storing users at all (link) but just to handle the authentication flows, I would expect this to be a less common usage but Auth0 facilitate it.

What is an Action Script and why are they are hard to unit test?

An Action Script is some custom code that is deployed into Auth0, it will be invoked under certain scenarios (eg. when a user logs in, link). There are various points at which Auth0 functionality can be extended/augmented in this way (rules, hooks, actions) and they all behave in slightly different ways (it’s a little frustrating). This post focuses on Action Scripts in the context of using a custom database.

The runtime in which these Action Scripts execute has some restrictions that your code must operate within (link). The relevant (and undocumented) one for this post is that top-level exports are not allowed. This makes structuring your code to facilitate unit testing a little tricky.

Effectively, neither of the following approaches will work:

export function handler() {
    // do something here
}

module.exports = {
    handler
}

The first approach using export will cause a syntax error and trying to access module will fail.

An approach to unit testing them Auth0 custom database Action Scripts

My approach to testing Cloudfront Functions using the rewire module is a possible solution to unit testing Auth0 custom database Action Scripts. It is not the one I used though, this is because if I can get away with not bringing in additional dependencies then that is desirable. Rewire is also a pretty invasive module that (I assume) fiddles with the inner workings of node’s module resolution process, which is a bit much if we don’t have to do it.

Whilst top-level exports are not permitted and module is undefined when accessed at the top-level, module is, surprisingly, available outside of the global scope. This is a perfect use case for an Immediately Invoked Function Expressions (IIFE). An IIFE is essentially a function that is run immediately after it has been defined but bounded in () that isolates its scope from the outer scope. This means it will be immediately invoked when the module is imported into the runtime which Auth0 has to do at some point to execute the code.

Taking advantage of these Javascript capabilities my first pass at writing a unit-testable Action Script looked something like this:

(() => {
    const doStuff = function() {
        console.log("handler ran");
    }

    if (module) {
        module.exports = {
            doStuff,
        }
    }

    return doStuff;
})()

The thinking was that because module is not available in the global scope, it isn’t available in the runtime at all. To account for this the exports are only set if module is defined, this means that when my unit tests attempt to import this module doStuff is importable. But when the module is executed in the Auth0 runtime trying to manipulate exports shouldn’t cause a runtime error and doStuff is available as an (almost) top level function.

This worked, handler ran is logged to the terminal in Auth0 when I tested it. Job done? Yes, and no. I pushed it a bit further and removed the conditional testing of module like so:

(() => {
    const doStuff = function() {
        console.log("handler ran");
    }

    module.exports = {
        doStuff,
    }

    return doStuff;
})()

I was very surprised when this worked, there were no runtime errors when I tested it in Auth0 and handler ran was logged to the terminal. I’m not really sure what is going on here, module not being available in the runtime is inconvenient but consistent. Whereas, module just not being available in the global scope is a little weird.

Auth0 do have a keen interest in securing the runtime for these custom database Action Scripts which is awesome, for example they vet third party modules to include in the runtime (link). Perhaps the behaviour witnessed is security related and they didn’t want exporting to be allowed (a la Cloudfront Functions), perhaps module being available in a nested scope is not intentional. It is though, and in this instance I took advantage of it.

Summary

Auth0 custom database Action Scripts exhibit some slightly odd runtime behaviour but with a little experimentation and taking advantage of some not always very useful Javascript features like IIFEs unit testing them was doable. Structuring your code a certain way to facilitate unit testing, as was the case here, I don’t consider a bad thing. Code that is hard to test is usually too complex or unobservable, if unit testing code helps to reduce the complexity, break it up or make it more observable then I consider that a good thing.

Get in contact

If you have comments, questions or better ways to do anything that I have discussed in this post then please get in contact.