Skip to content

Migrate off bento-auth-js onto Auth.js (next-auth v5) - #443

Merged
SanjeevLakhwani merged 23 commits into
mainfrom
feat/authjs-migration
Oct 2, 2026
Merged

SanjeevLakhwani merged 23 commits into
mainfrom
feat/authjs-migration

Conversation

@SanjeevLakhwani

@SanjeevLakhwani SanjeevLakhwani commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Replaces bento-auth-js's hand-rolled client-side PKCE/OIDC flow, Redux-held tokens, and popup reauth with Auth.js (next-auth v5): a custom OIDC provider against the same identity provider, JWT session strategy with server-side refresh-token rotation, and an httpOnly session cookie
  • Drops the popup-window reauth flow, the auto-authenticate-on-reload mechanism, and the token-refresh Web Worker - SessionProvider's session cookie + refetch behavior covers the same ground natively
  • retains shared knowledge files in bento-auth-js

Related infra changes (separate repos, not in this PR)

Adds a next-auth v5 config (custom OIDC provider against the same
identity provider, JWT sessions with manual refresh-token rotation)
and its Route Handler, and wires SessionProvider into the app. This
lands next to the still-active bento-auth-js so the new plumbing can
be reviewed in isolation before consumers are switched over.
Adds src/js/features/auth/ (resources, permission strings, and a
resource-permissions Redux slice, all ported from bento-auth-js since
they're Bento's own authorization-service contract, not part of the
OIDC library) and repoints every component/hook that used
bento-auth-js at it and at next-auth's useSession()/signIn()/signOut().

Session state (access/id tokens) now lives in Auth.js's own session,
not Redux/localStorage. Redux thunks that need the access token
synchronously (they only have getState(), no hook access) get it via
a small mirror kept in sync by useSyncAccessToken(), dispatched once
near the app root - the token itself is never persisted to
localStorage the way it used to be.

Sign-out now also ends the identity provider's own session
(RP-initiated logout via end_session_endpoint), matching prior
behaviour, via a small session-independent discovery route since
next-auth has no built-in support for it.

Per the migration plan, the popup-window reauth flow and the
localStorage-flag-driven auto-reauth ("was signed in, try silently
signing in again") are both dropped rather than ported: Auth.js's
persistent session cookie plus refresh-on-read already covers what
auto-reauth was for, and popup reauth would mean building custom code
back on top of the thing this migration is meant to simplify.

bento-auth-js itself is still a listed dependency for now; removing
it and the dead code it leaves behind (the token-refresh Web Worker)
is the next step.
Removes bento-auth-js and its hand-built postinstall step now that
nothing in the app imports it, and deletes the token-refresh Web
Worker it needed (bento-auth-js used it to work around setInterval
throttling in inactive tabs; SessionProvider's refetchInterval plus
its refetchOnWindowFocus default cover the same need without a
dedicated worker).
Auth.js's own config validation requires `issuer` even though
`wellKnown` is what it actually fetches from at request time - without
it, every request throws InvalidEndpoints and /api/auth/session 500s.
Derive it from OPENID_CONFIG_URL by stripping the well-known suffix.
…nstead of duplicating them

The Auth.js migration copied bento-auth-js's Resource type, permission
string constants, and makeAuthorizationHeader verbatim into
features/auth/ purely to drop the dependency - none of that is
OIDC-related, it's Bento's own authorization-service contract. Keeping
bento-auth-js as a dependency and importing those pieces directly
avoids maintaining a duplicate copy.

features/auth/authSlice.ts and hooks.ts stay as local code: they're
genuinely new logic bridging Auth.js's session to the resource-
permission cache, not something bento-auth-js's own (differently
wired) Redux slice and hooks can serve.
@SanjeevLakhwani
SanjeevLakhwani marked this pull request as ready for review September 9, 2026 17:20
@SanjeevLakhwani
SanjeevLakhwani force-pushed the feat/next-migration-step1 branch from 323efef to 8c6921d Compare September 9, 2026 20:14
Base automatically changed from feat/next-migration-step1 to main September 9, 2026 20:42
Previously a RefreshTokenError just sat on the session, so every
subsequent session check retried the same dead refresh token against
the identity provider and logged the same invalid_grant error forever.

@v-rocheleau v-rocheleau left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great direction for the changes, will make it a lot easier to use bento public with various OIDC providers with confidential clients, great for PCGL.

Just have 1 env vars question I wanted to ask before going deeper in the review.

Comment thread src/js/config.ts
Comment thread src/js/components/BentoAppRouter.tsx Outdated
Comment thread src/js/features/auth/authSlice.ts
Comment thread src/js/features/auth/authSlice.ts
Comment thread src/js/auth.ts Outdated
Comment thread src/js/auth.ts
Comment thread src/js/constants/configConstants.ts Outdated
getProjects ran on mount, before the session resolved and its token was
mirrored into Redux, so signed-in users' project list was fetched without
an Authorization header and never refetched. Gate that fetch and the
loader on a new useIsAuthReady hook, which stays true across token
refreshes so it doesn't re-trigger fetches.
Read BENTO_PUBLIC_SESSION_REFETCH_INTERVAL_SECONDS (default 120) through
the runtime config like the other display/auth settings, instead of a
hardcoded constant.
…nd sign-in

The provider id was written as a literal in both auth.ts and the client
hooks, and it also forms the /api/auth/callback/<id> path. Move it to a
dependency-free constants module both sides can import.
# Conflicts:
#	package-lock.json
#	package.json
#	src/js/components/SiteHeader.tsx

@davidlougheed davidlougheed left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • update igv.js code to use access token from features/auth?

Comment thread src/js/features/auth/hooks.ts
Comment thread src/js/auth.ts Outdated
Comment thread package.json
Comment thread src/js/config.ts
Comment thread src/js/features/auth/hooks.ts
Comment thread src/js/auth.ts Outdated
Comment thread src/js/components/Util/AuthOutlet.tsx
Comment thread src/js/auth.ts
…-js's

Main added IGV track support using bento-auth-js's useAccessToken, which
reads from a Redux auth slice bento-auth-js no longer populates on this
branch. It happened to work by coincidence (our slice also has an
accessToken field), but relies on an internal implementation detail
rather than the session. Use our own hook instead.
Comment thread src/js/auth.ts
Comment thread src/js/components/SiteHeader.tsx Outdated
…rect

performSignOut() clears the session client-side before redirecting to the
identity provider to end its session too, so isAuthenticated flips false
mid-callback and the button flashed to Sign In right before the page
navigated away. Track sign-out locally and keep the button loading
instead.
Comment thread src/js/auth.ts
Comment thread src/js/auth.ts

@davidlougheed davidlougheed left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@SanjeevLakhwani
SanjeevLakhwani merged commit ffeaa4d into main Oct 2, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants