Skip to content

Support a dedicated backend (BFF) - #118

Open
brionmario wants to merge 1 commit into
thunder-id:mainfrom
brionmario:feat-support-bff
Open

brionmario wants to merge 1 commit into
thunder-id:mainfrom
brionmario:feat-support-bff

Conversation

@brionmario

@brionmario brionmario commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

Purpose

Browser apps built on @thunderid/browser, @thunderid/react and @thunderid/vue keep their tokens in browser storage, where any script that runs on the page can read them. This adds an opt-in mode where sign-in runs on the app's own backend (the Backend for Frontend pattern from RFC 10017). The backend keeps the tokens behind an HttpOnly cookie, and the SDK never holds one.

Setting endpoints.backend turns it on. Unset paths default under /api/auth:

<ThunderIDProvider
  baseUrl="https://auth.example.com"
  endpoints={{backend: {signIn: '/api/auth/signin'}}}
>

Apps that do not set it keep working exactly as today.

Approach

  • Core. endpoints.backend takes signIn, session, checkSession, refresh, signOut and proxy. resolveBackendEndpoints fills in the defaults. The key stays out of the OIDC discovery metadata.
  • Browser. BackendClient talks to the backend. BackendHttpClient is registered as the instance's HTTP client, so every existing http.request call and the provider's /users/me fetch go through the backend's proxy with the session cookie and an X-Requested-With header, and never with a token. A request to a URL outside the API base is refused rather than sent without credentials.
  • Client surface. signIn, signOut, isSignedIn, getUser and getDecodedIdToken go through the backend. getAccessToken, getIdToken, exchangeToken and revokeAccessToken reject with a clear error, because the browser holds no token.
  • Loading. The client reads the session during initialize and reports loading until that read settles. Without this, route guards saw "not loading, not signed in" and started a sign-in, which the SSO session completed straight away, in a loop.
  • Refresh. The SDK refreshes only when the backend answers 401 session_expired. Any other 401 is the API's own answer and is returned as is.
  • Sign-out elsewhere. The SDK fires its sign-out hook when the proxy answers 401 login_required, when a check on page focus finds the session gone, or when polling checkSession does. Polling uses the existing syncSession and checkSessionInterval keys. The React and Vue providers now listen to the hook and clear the user, so a route guard sends the user to sign in. A sign-out the app starts itself does not fire it.
  • React. The embedded sign-in, sign-up and recovery flows are refused in this mode for now.

The backend contract, and the server implementation the ThunderID Console uses, are in the linked product PR.

Related Issues

  • N/A

Related PRs

Checklist

  • Followed the contribution guidelines.
  • Manual test round performed and verified. The ThunderID Console, built against this branch, signed in, made API calls, reloaded, created a user and signed out against a local server running the backend.
  • Documentation provided. (Add links if there are any)
  • Tests provided. (Add links if there are any)
    • Unit Tests
    • Integration Tests
  • Breaking changes. (Fill if applicable)
    • Breaking changes section filled.
    • breaking change label added.
  • Cross-SDK parity. Exactly one of parity/prs-raised or parity/prs-not-needed added.
    • If parity/prs-raised, the port links are posted as a reply on the parity check's comment.

Security checks

  • Followed secure coding standards.
  • Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets.

Setting endpoints.backend hands sign-in to the app's backend (the Backend for
Frontend pattern). The browser client then signs in, reads the user, and signs
out through the backend, and sends API calls through its proxy with the session
cookie, so the browser never holds a token. React and Vue pick it up through
the browser client.

The client reports loading until the first session read settles, so route
guards do not start a sign-in before it is known whether the user is signed in.
A session that ends elsewhere fires the sign-out hook, found by a 401
login_required from the proxy, a check when the page regains focus, or polling
/checksession when syncSession is on.

Signed-off-by: Brion <info@brionmario.com>
@github-actions

Copy link
Copy Markdown

🔀 Cross-SDK feature parity

Does this change need to ship in the other ThunderID SDKs too? This check stays red until one of these labels answers that:

Label What it asserts
parity/prs-raised The ports exist in every other SDK this change reaches, or are recorded as not applying there.
parity/prs-not-needed Nothing to port: a fix, refactor, docs, CI, dependency bump, release, or a change specific to this platform.

Ports (DO NOT EDIT)

  • thunder-id/ios-sdks, iOS
  • thunder-id/android-sdks, Android
  • thunder-id/flutter-sdks, Flutter

No sibling SDKs accounted for on this thread yet.

Important

Reply to this comment with this block, filled in. The boxes above tick
themselves when you post it.

- thunder-id/ios-sdks: <paste the port link here>
- thunder-id/android-sdks: <paste the port link here>
- thunder-id/flutter-sdks: <paste the port link here>

A link can be a pull request or a tracked issue, written in full or as thunder-id/ios-sdks#123.
Where the change does not reach an SDK, replace the placeholder with N/A and a short
reason, as in - thunder-id/ios-sdks: N/A, the capability has no equivalent there.

The contract is in the SDK development specification.

@coderabbitai

coderabbitai Bot commented Oct 14, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Repository: thunder-id/javascript-sdks/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 5745a9b6-7ab4-476a-a96c-12ca0f3f1888
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@brionmario brionmario changed the title Sign in through the app's backend in the browser SDKs Support a dedicated backend (BFF) Oct 14, 2026
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.

1 participant