Hi everyone,
I’m exploring the GSoC project to integrate O3 with the authentication module mentored by @Jayasanka. I’ve been going through the codebase and wanted to share what I’m thinking and get some guidance before I go further.
What I understand so far
The module works well for 2.x — TOTP, 2FA, secret question, force-password-change all work through JSP pages served by openmrs-module-authenticationui. The problem with O3 is that when authentication is incomplete, AuthenticationFilter redirects to those JSP challengeUrl pages (e.g. /loginTotp.page), which O3 simply can’t render.
The approach I’m thinking about
I noticed that AuthenticationFilter already has this special handling for the session endpoint:
if (WebUtil.matchesPath(request, "/ws/rest/*/session")) {
response.setHeader("Location", challengeUrl);
}
This is essentially what the project description calls a “fake redirect” — instead of a 3xx redirect, it’s a 401 with a Location header that this section of openmrsFetch reads and uses to navigate the SPA. So the trigger mechanism is already there, the challengeUrl just needs to point to O3 SPA routes.
Backend changes I think are needed
-
Make
challengeUrlvalues configurable to point to O3 SPA routes. SinceloginPageis already a config property on each scheme (e.g.authentication.scheme.totp.config.loginPage), this could be as simple as letting implementers set it to/spa/logininstead of/loginTotp.page. Existing 2.x setups like PIH’s would be unaffected since defaults stay the same. -
Extend the
AuthenticationFilterso that the401 + Locationbehavior applies not just to/ws/rest/*/sessionbut also to other REST calls that happen while auth is incomplete — so the SPA always gets redirected correctly regardless of which endpoint triggered the auth check. -
Add new REST endpoints to accept TOTP codes and secret question answers. Right now credentials are submitted via JSP form POST, which O3 can’t do. The frontend needs a REST endpoint to POST the TOTP code to and get back a completed session.
-
Possibly expose the current auth state (e.g. “primary factor done, TOTP pending”) in the session endpoint response so the O3 frontend knows which step to render.
On backward compatibility
Since all the challengeUrl values are driven by runtime properties and defaults stay unchanged, I believe existing 2.x setups would be completely unaffected. O3 behavior would be opt-in via config. Does that hold up in practice?
What I’m still thinking through
-
The
TwoFactorAuthenticationSchemeorchestrates primary + secondary auth across multiple requests, storing intermediate state in theHttpSession. When O3 submits credentials via REST, the session state needs to persist between the primary auth call and the secondary (TOTP/secret question) call. Is the currentHttpSession-based state management inUserLoginsufficient for this, or does it need changes to work reliably with a stateless REST flow? -
For the multi-step auth flow (e.g. username/password → TOTP), the frontend needs to know after the first step that primary auth succeeded but a second factor is still required, and specifically which factor (TOTP vs secret question — since that’s per-user via
authentication.secondaryTypeuser property). Should this state be communicated through theLocationheader URL (e.g./spa/login?step=totp), or should the session endpoint response body carry this information explicitly? -
The
ForcePasswordChangeFilterruns as a separate filter after authentication succeeds. Its redirect currently goes to a JSP page. Should the O3-compatible redirect from this filter follow the same401 + Locationpattern asAuthenticationFilter, or is a different approach more appropriate here since the user is already authenticated at that point?
Would really appreciate any guidance here. Thanks a lot!
CC @mseaton @jayasanka @ruhanga @mogoodrich @dkayiwa @ibacher @raff