So, as most of you know, both O1 (as we call it now) supported a flow for users to be required to change their passwords the first time they log in. This is a useful system to have in-place, because it allows assurance that administrative users do not know how to login as actual clinical users, so this has been a feature we’ve been trying to bring into O3 for a while. I’ve been working on this, and am hoping to have a fully-functional prototype in a couple of weeks, but I thought I’d write a post to explain the challenges porting this functionality to O3.
As I’m sure most of you know, O3 has two components, a single page application (the “frontend”) that is written almost entirely in Typescript and communicates with the OpenMRS platform (the “backend”) via REST and FHIR APIs. This means it functions a bit differently than either O1 or O2 UIs do, and specifically, we’ve been promoting the separation of these two layers by demonstrating (via the RefApp) that it’s possible to deploy the frontend layer either via a separate, arbitrary webserver (we use nginx in the RefApp, but anything should work) or via a backend module (module-spa). This is where the challenge comes in.
To explain, let’s walk through how the force password change feature works. In the Legacy UI, this is actually pretty simple feature. We leverage Java’s servlet filter API, which allows us to define code that “intercepts” any web requests before they are fully processed and redirect them. So, to implement the “user must change password” flow, all we needed to do was add a filter that works on every request and simply checks whether:
- The user is authenticated.
- The user has been flagged to require changing their password.
- The request is not one of the requests necessary to render the change password page (i.e., it’s not the
/changePassword.form
URL or one of the JS or CSS files loaded on that page).
If we get a request that meets those criteria, instead of sending the request to be normally processed, we simply redirect the user to the “/changePassword.form” URL.
This allows us to implement something like the following user story: “As a new user, if I am required to change my password, I cannot use the application until I do so.”
Unfortunately, in O3, all of this gets a little bit more complex. We can still check, on every request, whether the user is authenticated and if they are authenticated, whether they have been flagged to change their password, but step 3 in the process is not quite as straight-forward in O3 as it was in O1. This is because in O3, as single page application, we generally don’t make requests that expect an HTML document as a response. Instead, we use the browser’s Fetch API to make requests to a REST or FHIR endpoint.
Because we’re using the fetch API, if we return a response with a redirect, this doesn’t change the page that the user is looking at. Instead, it simply changes the fetch API response from the expected JSON result to an HTML page. One—very bad approach—to fixing this would be to require each app, extension, and anywhere else we’re making a request to handle the case where it got back and HTML response, and try to replace the whole single page with that response. However, since OpenMRS is structured as a number of microfrontends which might all be making requests to the backend simultaneously, there’s a lot of room for race conditions and similar problems.
To get around this, I took the approach of designing a mini-protocol. Basically, what this means is that if the backend gets a request from a user who is authenticated but is required to change their password, we send a standard HTTP 401 response, indicating that it is unauthorized, with a Location
header pointing to the URL the user needs to go to change their password. Then, on the frontend side, we simply update the openmrsFetch()
function to look for this and send the user to the appropriate change-password page.
Conceptually, I think this approach is pretty simple and should work well, but it does leave us with a few points that need to be worked out. Specifically, the part that’s still not completely done yet is ensuring that the user is unable to load any clinically relevant data, but is able to load the data (and resources) that make the change password page work. This turns out to require a lot more fine-tuning, testing and verification. So while we have a feature that is, in my estimation, 80% complete or so, the final 20% will take a fair bit of work.