# GSoC 2026: O3 Frontend Architecture for Authentication Module Integration

**URL:** <https://talk.openmrs.org/t/gsoc-2026-o3-frontend-architecture-for-authentication-module-integration/49131>\
**Category:** Development\
**Tags:** authentication\
**Created:** [March 28, 2026, 8:33pm UTC](https://talk.openmrs.org/t/gsoc-2026-o3-frontend-architecture-for-authentication-module-integration/49131 "2026-03-28T20:33:24Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![praneeth622](https://talk.openmrs.org/user_avatar/talk.openmrs.org/praneeth622/32/22128_2.png) [@praneeth622](https://talk.openmrs.org/u/praneeth622)\
**Post date:** [March 28, 2026, 8:33pm UTC](https://talk.openmrs.org/t/gsoc-2026-o3-frontend-architecture-for-authentication-module-integration/49131/1 "2026-03-28T20:33:24Z")

</div>

Hey @jayasanka @ibacher,

Following up on my [earlier post](https://talk.openmrs.org/t/brainstorming-gsoc-2026-project-ideas/47974/40) — I’ve since done a deeper dive into the auth module codebase and @twiine’s [thread](https://talk.openmrs.org/t/seeking-guidance-integrate-o3-with-the-authentication-module/44893). Rather than repeat the backend analysis, I want to focus on the **frontend side** that hasn’t been discussed yet.

Quick background: I’m a final-year CSE student at IIITDM Kancheepuram, currently interning at [Attack Capital](https://attack.capital/) (YC W-22) where I’m building HealOS, a healthcare EHR platform. Day job involves Stripe billing infrastructure, AI document pipelines, and yes, I migrated our entire auth system from Clerk to Firebase, handling MFA flows, token management, and session state machines in a React SPA. That experience is directly relevant here. Been contributing to O3 since October 2025 across [esm-patient-chart](https://github.com/openmrs/openmrs-esm-patient-chart) and [esm-patient-management](https://github.com/openmrs/openmrs-esm-patient-management) — @jayasanka reviewed my [O3-5272](https://github.com/openmrs/openmrs-esm-patient-management/pull/2349) work and pushed me to rewrite it from hardcoded to template-driven, which honestly made the solution much better.

### Where O3 breaks today

The login flow in [openmrs-esm-core](https://github.com/openmrs/openmrs-esm-core) sends a `Basic` header to `/ws/rest/v1/session` and treats any `401` as “wrong credentials.” But with the auth module active, a `401 + Location` header means “primary auth passed, go complete this challenge.” `openmrsFetch` doesn’t check for the `Location` header — so the user just sees a login error with no idea they need to enter a TOTP code.

### How the frontend flow should work

Following @ibacher’s [guidance](https://talk.openmrs.org/t/seeking-guidance-integrate-o3-with-the-authentication-module/44893/2) about not exposing auth state:

1. `openmrsFetch` detects `401 + Location` → navigates SPA to that route (e.g. `/spa/login/totp`)
2. Challenge component renders, user submits, POSTs JSON to REST endpoint
3. Another `401 + Location`? → follow it. `200` with session? → done, enter the app.

The frontend stays **completely stateless** — it just follows the backend’s `Location` headers. `UserLogin` and `TwoFactorAuthenticationScheme` already manage the state machine server-side.

I built something very similar at my current job — migrated auth from Clerk to Firebase in a React SPA, handling MFA intermediate states without storing sensitive auth state on the client. Same pattern, different codebase.

### Forced password change

Since @ibacher noted a `401` doesn’t fit here (user IS authenticated), what about a `requirePasswordChange: true` flag in the session response? The login app checks it after successful auth and routes to `/spa/login/change-password`. No filter changes, no redirect — just a flag in a response we already read.

### Backward compatibility

If there’s no `Location` header on a `401`, everything works exactly as today. MFA routes stay dormant. Zero impact on deployments not using this module.

### Questions

1. REST endpoints for TOTP/secret question JSON submission — in this module (extending [PR #18](https://github.com/openmrs/openmrs-module-authentication/pull/18)’s `TotpController`), or in `openmrs-module-webservices.rest`?

2. `requirePasswordChange` flag in session response — viable approach?

3. Should `401 + Location` interception be global in `openmrsFetch` or scoped to login only?

cc @twiine @mseaton

---

<div class="post-metadata">

**Author:** ![praneeth622](https://talk.openmrs.org/user_avatar/talk.openmrs.org/praneeth622/32/22128_2.png) [@praneeth622](https://talk.openmrs.org/u/praneeth622)\
**Post date:** [April 17, 2026, 5:43am UTC](https://talk.openmrs.org/t/gsoc-2026-o3-frontend-architecture-for-authentication-module-integration/49131/2 "2026-04-17T05:43:11Z")

</div>

Following up on this thread my GSoC 2026 proposal for this project was submitted on Mar 31, but I wanted to keep the technical discussion moving.

A couple of frontend questions for @jayasanka and @janithrs that came up while I was prototyping:

1. The current `openmrs-esm-primary-navigation-app` login flow expects the legacy JSESSIONID + 302 redirect. With the auth-module moving toward the SPA-friendly Location-header + JSON contract (ref PR #29 work), what’s the preferred interim strategy — feature-flag the new client, or version the auth response and let the frontend negotiate?

2. For `requirePasswordChange: true` in the auth response, does the O3 side want this routed through an extension slot (so distros can override the reset UI), or a hard-coded post-login interstitial?

3. For 2FA/TOTP challenge flows, leaning toward client polling the auth status endpoint vs. EventSource — any preference from a module-side perspective?

I’ve been sketching these on a fork of esm-primary-navigation. Happy to open a draft PR anchored to this thread if it’s useful.

Praneeth

---

<div class="post-metadata">

**Author:** ![ashthe25](https://talk.openmrs.org/user_avatar/talk.openmrs.org/ashthe25/32/22144_2.png) [@ashthe25](https://talk.openmrs.org/u/ashthe25)\
**Post date:** [April 20, 2026, 3:14am UTC](https://talk.openmrs.org/t/gsoc-2026-o3-frontend-architecture-for-authentication-module-integration/49131/4 "2026-04-20T03:14:42Z")

</div>

Very interesting conversation, particularly regarding the `401 + Location` flow for maintaining the server-driven authentication state machine. I believe this is elegant because it allows the frontend to stay mostly stateless and follow the challenge transitions.

+1 for the question about whether interception should be done globally for `openmrsFetch` versus being login-specific.

I’ve also been looking into the O3/auth flow, so I’ll be following along here. I’m happy to help out on the frontend side if needed.

— Ash

---

<div class="post-metadata">

**Author:** ![praneeth622](https://talk.openmrs.org/user_avatar/talk.openmrs.org/praneeth622/32/22128_2.png) [@praneeth622](https://talk.openmrs.org/u/praneeth622)\
**Post date:** [April 20, 2026, 9:57am UTC](https://talk.openmrs.org/t/gsoc-2026-o3-frontend-architecture-for-authentication-module-integration/49131/5 "2026-04-20T09:57:24Z")

</div>

Thanks @ashthe25 glad you’re looking into this too.

On the global vs login-specific interception question: my lean is global, for one reason. Any authenticated request can receive a 401 + Location challenge mid-session session expiry, step-up auth, privilege escalation triggers. A login-specific interceptor would miss those and punt the user to a generic error screen with no recovery path. The scoping risk is making sure we don’t accidentally intercept 401s that aren’t auth-module challenges. If there’s no Location header, that’s a genuine auth failure and openmrsFetch should hand it back to the caller as it does today.

If you want a concrete starter that would actually help: there’s no existing regression test in openmrs-esm-core that exercises the 401-redirect path in openmrsFetch against a mocked auth-module response. Writing one would give us a guard for whatever the final design lands on, and it’s a scoped piece that doesn’t depend on the rest of the architecture being decided.

---

<div class="post-metadata">

**Author:** ![praneeth622](https://talk.openmrs.org/user_avatar/talk.openmrs.org/praneeth622/32/22128_2.png) [@praneeth622](https://talk.openmrs.org/u/praneeth622)\
**Post date:** [April 20, 2026, 1:24pm UTC](https://talk.openmrs.org/t/gsoc-2026-o3-frontend-architecture-for-authentication-module-integration/49131/6 "2026-04-20T13:24:57Z")

</div>

Following up on the Auth integration thread. Been reading through `openmrs-module-authentication` today with ibacher’s “maintain the same interfaces” framing in mind.

One scoping question: the existing `AuthenticationScheme` implementations (`Basic`, `Totp`, `SecretQuestion`, and the new `EmailAuthenticationScheme` from AUT-20) already express the server-side flow well. The less-settled piece seems to be the response shape for multi-step auth when the caller is a SPA @sudeshkar #37 prototypes one direction with an SPA-friendly failure response.

For the 2026 project, is the intent to (a) keep the schemes untouched and adapt O3 to the existing `loginLocation` redirect model, (b) standardize a JSON response contract around `AuthenticationContext` transitions that the frontend consumes, or (c) both in sequence? Asking because the scoping changes where most of the 12-week work lives.

@jayasanka

---

<div class="post-metadata">

**Author:** ![sudeshkar](https://talk.openmrs.org/user_avatar/talk.openmrs.org/sudeshkar/32/22138_2.png) [@sudeshkar](https://talk.openmrs.org/u/sudeshkar)\
**Post date:** [April 20, 2026, 2:21pm UTC](https://talk.openmrs.org/t/gsoc-2026-o3-frontend-architecture-for-authentication-module-integration/49131/7 "2026-04-20T14:21:22Z")

</div>

Hi @jayasanka , I’m also following this closely as the author of PR #37. Happy to clarify the design decisions behind it and adjust based on your feedback on the preferred direction.

---

<div class="post-metadata">

**Author:** ![ashthe25](https://talk.openmrs.org/user_avatar/talk.openmrs.org/ashthe25/32/22144_2.png) [@ashthe25](https://talk.openmrs.org/u/ashthe25)\
**Post date:** [April 20, 2026, 5:34pm UTC](https://talk.openmrs.org/t/gsoc-2026-o3-frontend-architecture-for-authentication-module-integration/49131/8 "2026-04-20T17:34:50Z")

</div>

Thanks for the direction. I’ll write the regression test for the 401-redirect path in openmrsFetch against a mocked auth-module response and open a PR. Will share once it’s ready.

---

<div class="post-metadata">

**Author:** ![ashthe25](https://talk.openmrs.org/user_avatar/talk.openmrs.org/ashthe25/32/22144_2.png) [@ashthe25](https://talk.openmrs.org/u/ashthe25)\
**Post date:** [April 20, 2026, 5:56pm UTC](https://talk.openmrs.org/t/gsoc-2026-o3-frontend-architecture-for-authentication-module-integration/49131/9 "2026-04-20T17:56:37Z")

</div>

Hi @praneeth622, I went ahead and added those regression tests for the 401-redirect path in `openmrsFetch` . PR here: [(test) Add regression tests for 401 Location-header redirect in openmrsFetch by AshThe25 · Pull Request #1754 · openmrs/openmrs-esm-core · GitHub](https://github.com/openmrs/openmrs-esm-core/pull/1754) — covers both the Location-header redirect and the fallback case. Let me know if the test coverage looks right to you

---

<div class="post-metadata">

**Author:** ![ibacher](https://talk.openmrs.org/user_avatar/talk.openmrs.org/ibacher/32/19751_2.png) [@ibacher](https://talk.openmrs.org/u/ibacher)\
**Post date:** [April 20, 2026, 8:12pm UTC](https://talk.openmrs.org/t/gsoc-2026-o3-frontend-architecture-for-authentication-module-integration/49131/10 "2026-04-20T20:12:35Z")

</div>

> [@praneeth622](#):
>
> REST endpoints for TOTP/secret question JSON submission — in this module (extending [PR #18](https://github.com/openmrs/openmrs-module-authentication/pull/18)’s `TotpController`), or in `openmrs-module-webservices.rest`?

`webservices.rest` provides the core functionality for a REST layer and mappings for stuff in core. Anything not in core is handled elsewhere.

> [@praneeth622](#):
>
> `requirePasswordChange` flag in session response — viable approach?

This feels like it’s introducing a lot of machinery for very little benefit. A user with the `requirePasswordChange` _must_ change their password before they can take any other actions in the system.

> Should `401 + Location` interception be global in `openmrsFetch` or scoped to login only?

It kind of needs to be global, at least for anything that hits any endpoint in OpenMRS. Otherwise, we can never expire sessions, require users to enter passwords or re-authenticated, etc.

---

<div class="post-metadata">

**Author:** ![ashthe25](https://talk.openmrs.org/user_avatar/talk.openmrs.org/ashthe25/32/22144_2.png) [@ashthe25](https://talk.openmrs.org/u/ashthe25)\
**Post date:** [April 21, 2026, 3:40am UTC](https://talk.openmrs.org/t/gsoc-2026-o3-frontend-architecture-for-authentication-module-integration/49131/11 "2026-04-21T03:40:11Z")

</div>

@ibacher makes sense regarding the global — an authenticated endpoint can initiate a session challenge; restricting the scope to the login will completely ignore any mid-session expiration.

Regarding #1754, the redirecting of the interceptor to the Location header’s URL should include the window.location.href as the returnUrl parameter, which will help to redirect the user once authenticated. The login component currently uses the location.state.referrer technique — just wondering whether it should include this mechanism too or whether the redirect is beyond its scope.

---

<div class="post-metadata">

**Author:** ![praneeth622](https://talk.openmrs.org/user_avatar/talk.openmrs.org/praneeth622/32/22128_2.png) [@praneeth622](https://talk.openmrs.org/u/praneeth622)\
**Post date:** [April 21, 2026, 6:00am UTC](https://talk.openmrs.org/t/gsoc-2026-o3-frontend-architecture-for-authentication-module-integration/49131/12 "2026-04-21T06:00:42Z")

</div>

Thanks @ibacher, that clarifies things:

1. TOTP / secret-question JSON in `webservices.rest` rather than the auth module. Makes sense, keeps the 2026 frontend work scoped to consuming REST endpoints instead of building REST inside the auth module.

2. On `requirePasswordChange`: fair point, the extension-slot idea was overkill. Dropping it. On that response we just hard-route to the password-change page, and nothing else is navigable until it’s cleared.

3. Global 401 + Location interception in `openmrsFetch`: aligned. That’s the only way session expiry and step-up challenges surface correctly, not just fresh logins.

Also @ashthe25 thanks for opening #1754, scope looks right. Quick thought on your returnUrl question: I’d encode `returnUrl=window.location.href` into the Location redirect as a query param. The current `location.state.referrer` pattern only survives inside the login router’s state, so a session expiry on an arbitrary page would lose it. Encoding it in the URL covers both fresh-login and mid-session paths with one mechanism.

---

<div class="post-metadata">

**Author:** ![ashthe25](https://talk.openmrs.org/user_avatar/talk.openmrs.org/ashthe25/32/22144_2.png) [@ashthe25](https://talk.openmrs.org/u/ashthe25)\
**Post date:** [April 21, 2026, 7:11am UTC](https://talk.openmrs.org/t/gsoc-2026-o3-frontend-architecture-for-authentication-module-integration/49131/13 "2026-04-21T07:11:10Z")

</div>

@praneeth622 I agree with you, we should encode it in the URL itself. If we use location.state.referrer, then it will exist only in the memory of the router and therefore, if the session expires halfway through navigating to a page, then the path to go back will be lost. We could encode returnUrl=window.location.href in the URL itself when using openmrsFetch.

---

<div class="post-metadata">

**Author:** ![praneeth622](https://talk.openmrs.org/user_avatar/talk.openmrs.org/praneeth622/32/22128_2.png) [@praneeth622](https://talk.openmrs.org/u/praneeth622)\
**Post date:** [April 21, 2026, 4:53pm UTC](https://talk.openmrs.org/t/gsoc-2026-o3-frontend-architecture-for-authentication-module-integration/49131/14 "2026-04-21T16:53:34Z")

</div>

Quick update on the Basic auth parsing side of the integration work: openmrs-module-authentication#48 goes in today. Two concrete fixes:

1. Authorization header prefix match is now case-insensitive per RFC 7235 §2.1 (previously a lowercase "basic " would fail).
2. Decoded credentials split on the first colon only, so passwords containing “:” are preserved (RFC 7617 §2).

Tests cover both. openmrs-esm-core#1751 (O3-4020, context sync on first render) merged on Apr 20 and unblocks the useAppContext path we discussed on the RFC

---

<div class="post-metadata">

**Author:** ![sudeshkar](https://talk.openmrs.org/user_avatar/talk.openmrs.org/sudeshkar/32/22138_2.png) [@sudeshkar](https://talk.openmrs.org/u/sudeshkar)\
**Post date:** [April 22, 2026, 10:09am UTC](https://talk.openmrs.org/t/gsoc-2026-o3-frontend-architecture-for-authentication-module-integration/49131/15 "2026-04-22T10:09:27Z")

</div>

Thanks @ibacher for the clarification. I’ve been following this closely as the author of PR #37. I’ll be updating the PR this Saturday to align with the global Location-header interception approach and move the TOTP endpoint to webservices.rest. Happy to discuss any further design decisions in the meantime.

---

<div class="post-metadata">

**Author:** ![praneeth622](https://talk.openmrs.org/user_avatar/talk.openmrs.org/praneeth622/32/22128_2.png) [@praneeth622](https://talk.openmrs.org/u/praneeth622)\
**Post date:** [April 22, 2026, 1:51pm UTC](https://talk.openmrs.org/t/gsoc-2026-o3-frontend-architecture-for-authentication-module-integration/49131/16 "2026-04-22T13:51:09Z")

</div>

+1 on moving the TOTP endpoint to webservices.rest consistent with how the rest of the auth surface routes through there. On the Location-header interception side, is the intent to intercept at the framework level in esm-framework or closer to the fetch layer? The answer probably decides whether the frontend needs its own redirect guard for cross-tab session expiry.

---

<div class="post-metadata">

**Author:** ![ibacher](https://talk.openmrs.org/user_avatar/talk.openmrs.org/ibacher/32/19751_2.png) [@ibacher](https://talk.openmrs.org/u/ibacher)\
**Post date:** [April 22, 2026, 5:14pm UTC](https://talk.openmrs.org/t/gsoc-2026-o3-frontend-architecture-for-authentication-module-integration/49131/17 "2026-04-22T17:14:35Z")

</div>

> [@sudeshkar](#):
>
> move the TOTP endpoint to webservices.rest.

This is the exact opposite of what I meant. All secondary authentication factors run through the authentication module and the REST endpoints should remain there. REST cannot depend on authentication _and_ it should not implement authentication functionality on its own.

As a general principle, supporting the authentication module in O3 should only require a minimal set of changes to the authentication module and no other backend code.

---

<div class="post-metadata">

**Author:** ![ashthe25](https://talk.openmrs.org/user_avatar/talk.openmrs.org/ashthe25/32/22144_2.png) [@ashthe25](https://talk.openmrs.org/u/ashthe25)\
**Post date:** [April 22, 2026, 10:49pm UTC](https://talk.openmrs.org/t/gsoc-2026-o3-frontend-architecture-for-authentication-module-integration/49131/18 "2026-04-22T22:49:22Z")

</div>

Cross-tab session expiry: #1754 addresses single tab scenario, where 401 + Location header fires navigate() call to authenticate URL. Cross tab session expiry would require listening to BroadcastChannel or storage events in the framework. This seems to be an extension area after we are comfortable with single tab scenario.

---

<div class="post-metadata">

**Author:** ![sudeshkar](https://talk.openmrs.org/user_avatar/talk.openmrs.org/sudeshkar/32/22138_2.png) [@sudeshkar](https://talk.openmrs.org/u/sudeshkar)\
**Post date:** [April 23, 2026, 10:02am UTC](https://talk.openmrs.org/t/gsoc-2026-o3-frontend-architecture-for-authentication-module-integration/49131/19 "2026-04-23T10:02:18Z")

</div>

Apologies @ibacher — I misread the earlier discussion. PR #37 keeps all authentication endpoints within the auth module. The only change is a minimal update to AuthenticationFilter to support SPA clients. I’ll update the PR on Saturday to reflect that scope clearly.

---

<div class="post-metadata">

**Author:** ![praneeth622](https://talk.openmrs.org/user_avatar/talk.openmrs.org/praneeth622/32/22128_2.png) [@praneeth622](https://talk.openmrs.org/u/praneeth622)\
**Post date:** [April 24, 2026, 1:47pm UTC](https://talk.openmrs.org/t/gsoc-2026-o3-frontend-architecture-for-authentication-module-integration/49131/20 "2026-04-24T13:47:29Z")

</div>

Agreed on webservices.rest as the target for the TOTP endpoint, aligning with how the rest of the auth surface is already routed there makes the frontend side much simpler to reason about.

On the Location-header interception: one detail that tripped me up while working on esm-core#1751 is that the redirect target often arrives before the SPA has a hydrated session, so useAppContext used to return a stale/empty value on the first render after the redirect. #1751 fixes that synchronous-first-render case, but it doesn’t cover cross-tab session expiry, if tab A redirects and tab B still holds a stale JSESSIONID, B’s next fetch 401s without any shared signal.

Two questions on the scheme you’re landing on:

1. Is the 401 → Location redirect meant to be handled purely at the fetch wrapper layer, or does the esm-framework session store also need to observe it so subscribers can flush?
2. For TOTP specifically, is the intent a two-step flow (password → 302 → TOTP endpoint), or are both credentials submitted in a single authenticate() call?

---

<div class="post-metadata">

**Author:** ![ibacher](https://talk.openmrs.org/user_avatar/talk.openmrs.org/ibacher/32/19751_2.png) [@ibacher](https://talk.openmrs.org/u/ibacher)\
**Post date:** [April 24, 2026, 3:08pm UTC](https://talk.openmrs.org/t/gsoc-2026-o3-frontend-architecture-for-authentication-module-integration/49131/21 "2026-04-24T15:08:31Z")

</div>

> [@praneeth622](#):
>
> cross-tab session expiry

FWIW, cross-tab session expiry is not something I’d worry about for now. We don’t have a pro-active form of session expiry at all.

> does the esm-framework session store also need to observe it

It really depends on what the 401 indicates…

> [@praneeth622](#):
>
> For TOTP specifically, is the intent a two-step flow

Two-step flow, but you can’t use a 3xx response with `fetch`, hence it will probably look something more like a 200 response with a Location header.

[Next page](https://talk.openmrs.org/t/gsoc-2026-o3-frontend-architecture-for-authentication-module-integration/49131.md?page=2)
