Rolling your own authentication for REST API's, part 3 - Anonymity is a warm blanket
Another aspect of this approach to authentication that may at first seem counter-intuitive is that, even if a valid token is passed with an API request, the user will not, in fact, be authenticated... that is, as far as Domino can determine. All data submitted and requested by consumer applications will be handled anonymously. You might well ask, "then what's the point?" Firstly, as was mentioned previously, we don't know that our consumers will handle cookies the same way that browsers do, and are therefore forced to circumvent Domino's standard authentication mechanism anyway.
Secondly, this allows us to discard our existing definition of a "user"; for most situations where we would use this technique, of course, one "user" will equal one human, but this alternate approach to authentication frees us up to muddy those waters, if desired, without risk of doing the same to our Domino Directory – or even an alternate directory defined via Directory Assistance. This becomes especially important if our application experiences viral adoption, because the performance degradation that would be experienced using traditional authentication against a Domino Directory containing millions of registered users would render the service unusable. The technique this article proposes, however, offers practically linear scalability when supporting an enormous user base.
Finally, ensuring that all data is accessed and submitted anonymously allows us to lock down our application completely. This is, of course, the most counter-intuitive statement of all. But this brings us to the first change planned for 8.5.2 that will be crucial to this technique: the ability to flag an XPage as available to "public access" users.
Domino has long supported a seemingly contradictory set of permissions in access control lists: an ACL entry can be listed as having no access, yet can also be granted the ability to read and/or write public documents (which includes design elements flagged for public access). This allows us to create a "welcome page" for users who do not yet have access to the application; this page typically describes the nature of the application and provides either a direct method for obtaining access to it or a description of that process. Even contact information for someone who controls access to the application is more useful than what the user experienced prior to the addition of public access ACL flags: being told simply to go away. Unfortunately, prior to 8.5.2, XPages could not be flagged as public access design elements, so an application relying primarily on XPage elements for its interface was forced either to use other design elements to provide such a home page or revert to the "just go away" approach to greeting users who did not already have access.
Because in 8.5.2 XPages can be flagged as public access elements, we can not only use them as welcome pages for authenticated and anonymous users alike, we even have the option of completely reversing our traditional application access model: we can, in fact, prevent all users from accessing the application and only permit access by anonymous users... but only to XPages flagged for public access. How does this make our application more secure? It forces all interactions with application data to go through the API that we provide, preventing all methods of direct access to data. Nobody but the server and the application signer has access to the underlying data, so no data is accepted or sent unless our code determines that the request to do so is valid.
This, in turn, brings us to the second change in 8.5.2 that is crucial to this technique: the new sessionAsSigner global variable in SSJS (server-side JavaScript). In an XPage, all code runs under the permissions of the user accessing the page. This is convenient but limiting: unlike agents, which by default run as the signer but can be flagged to run on behalf of the user, XPage code does not even have the option of performing operations with the signer's permissions. In 8.5.2, however, a new global variable, sessionAsSigner, has been added. This variable is identical to the existing session variable, except that it provides an entry point to the Domino API that brings with it the current permissions of the ID last used to sign the XPage. As a result, any operations performed on any Domino objects accessed via the session variable still run as the user; any operations performed on any Domino objects accessed via sessionAsSigner, however, run as the signer. With this change, XPages have less limitations than agents, because user and signer permissions can be fluidly mixed, not only within the same design element, but within the same code event – indeed, if desired, even within the same line of code. This can, of course, cause disastrous problems if used unwisely, but also offers great flexibility when used appropriately.
For our purposes, this new capability allows us to rely upon the anonymous user's permissions only to tell us which database we are currently in, and immediately pass control to the signer to determine whether the API consumer will be permitted to perform the operation described in the API request. Again we are confronted with an apparent contradiction: as an anonymous user with no access to the current application, why would we rely on the user's permissions to identify the current database and not the signer's? As strange as it might seem, the sessionAsSigner instance of the Java Session class has no current database. While it might not be an entirely accurate depiction of what is going on behind the scenes of the Domino Java API, we can consider this variable to be an instantiation of a "new" session with no corresponding database. Our anonymous user, however, does have public access to the current database, which is sufficient to tell us its location – specifically, the server and filepath used to access it. As a result, the following SSJS code gives us a valid entry point to the current database that will access it using the signer's permissions:
var signerDatabase:NotesDatabase = sessionAsSigner.getDatabase(database.getServer(), database.getFilePath());
Once we have established that database handle, any methods performed against it that attempt to access or create documents within it will do so on behalf of the signer. The same exact methods performed against the global database variable, however, would continue to run under the user's permissions – which, in our case, would be denied access to any data.
We now have a way to ensure that anonymous users can interact with our application's data, but only to the extent that our code determines they should be permitted... so let's dive into how our code will make that determination.
