Skip to content

BFF: baseUrl is the backend, and permissions come from the session - #62

Open
brionmario wants to merge 1 commit into
thunder-id:mainfrom
brionmario:granted-scopes-backend-session
Open

brionmario wants to merge 1 commit into
thunder-id:mainfrom
brionmario:granted-scopes-backend-session

Conversation

@brionmario

@brionmario brionmario commented Oct 30, 2026 •

Copy link
Copy Markdown
Member

Purpose

Two changes to backend mode (endpoints.backend, sign-in on a backend the app owns, RFC 10017 §6.1), ported alongside the JavaScript SDKs.

1. baseUrl is the backend, and the API is reached only through its proxy. Until now the backend's address was endpoints.backend.baseUrl, and the top-level baseUrl meant the API whose requests go through the proxy. In backend mode the app never talks to the ThunderID server or to the API: it talks to the backend, which holds the tokens and decides where API requests go. So the top-level baseUrl is now the backend, and the app's API base is the backend's proxy. The client no longer needs, or is told, the API's real address.

2. Permissions come from the session. In backend mode the access token stays on the backend, but getGrantedPermissions() read the permissions by decoding the access token, which getAccessToken() asks the backend's token endpoint for. A BFF doesn't offer one, since keeping tokens away from the client is the point of the pattern. The app saw no permissions, and GrantedPermissionsNotifier hid everything. A client learns what it was granted from the token response's scope (RFC 6749 §5.1), so the backend reports it with the session and the app reads it from there.

Approach

  • Resolution (resolve_backend_endpoints.dart): the top-level baseUrl must use HTTPS. Each endpoint is a path that resolves against baseUrl, or an absolute HTTPS URL that's used as it is; anything else is refused before any network call. BackendEndpoints.baseUrl is removed.
  • API requests (BackendClient.toProxyUrl), all going through the proxy with the session handle, the backend attaching the access token:
    • a path starting with / goes to baseUrl + proxy + the path;
    • a URL already under the proxy, or a path already starting with the proxy's path, is sent as it is, not prefixed again;
    • any other absolute URL is refused unless it's under a bypass URL.
  • Management: collection URLs are built on the proxy (baseUrl + proxy + the collection), and requests go through the backend's HTTP client with the session handle and no access token: none is requested from the backend. Collection overrides in endpoints still win. The client's config is now kept in backend mode, so management no longer throws there.
  • Session model: BackendSession gains an optional scope, parsed in fromMap, so every session read keeps it.
  • Permissions: in backend mode getGrantedPermissions() reads only the session's scope and never calls getAccessToken(). Redirect mode still reads the access token's scope or scopes claim.
  • Playground: THUNDERID_BACKEND_URL becomes the SDK's baseUrl; the backend's proxy fronts the ThunderID server.
  • README: a "Sign-in through a backend" section with the rules above.

Related Issues

  • N/A

Related PRs

Checklist

  • Followed the contribution guidelines.
  • Manual test round performed and verified.
  • Documentation provided. (Add links if there are any)
    • README, "Sign-in through a backend".
  • Tests provided. (Add links if there are any)
    • Unit Tests (test/resolve_backend_endpoints_test.dart: the backend in baseUrl, HTTPS for it, an absolute HTTPS endpoint, refused endpoints. test/backend_mode_test.dart: a relative path through the proxy, a URL already under the proxy not prefixed again, URLs outside the proxy refused, management URLs on the proxy and sent without an access token, and the "granted permissions" group.)
    • 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.

Not run locally: Flutter isn't installed on the machine this was written on, so flutter analyze and flutter test, including the new tests, haven't run. CI is the first run of both.

Security checks

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

Every backend URL, including an absolute endpoint, must use HTTPS. In backend mode the client never asks for an access token: management requests and API calls go through the backend's proxy with the session handle, and permissions come from the session. The permissions decide only what the app shows; the server still checks every request.

@brionmario brionmario added the parity/prs-raised Ports exist in the other SDKs, or are recorded as not applying there label Oct 30, 2026
@coderabbitai

coderabbitai Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

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

Run configuration
  • 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.

@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

🔀 Cross-SDK feature parity, ports raised

This pull request is labelled parity/prs-raised, so the capability it introduces is expected in the other ThunderID SDKs as well.

Ports (DO NOT EDIT)

  • thunder-id/javascript-sdks, JavaScript (React, Vue, Nuxt, Next.js, Express, React Router, TanStack Router, Browser, Node) => #148, #149, #150
  • thunder-id/ios-sdks, iOS => #41
  • thunder-id/android-sdks, Android => #49

3 of 3 sibling SDKs accounted for.

Every sibling SDK is accounted for.

The contract is in the SDK development specification.

@brionmario

Copy link
Copy Markdown
Member Author

@brionmario
brionmario force-pushed the granted-scopes-backend-session branch from 246b562 to 36e3f3d Compare October 30, 2026 08:44
@brionmario brionmario changed the title Read granted permissions from the backend session in backend mode Backend mode: baseUrl is the backend, and permissions come from the session Oct 30, 2026
@brionmario brionmario changed the title Backend mode: baseUrl is the backend, and permissions come from the session BFF: baseUrl is the backend, and permissions come from the session Oct 30, 2026
DonOmalVindula
DonOmalVindula previously approved these changes Oct 30, 2026
When sign-in runs on a backend (endpoints.backend), the top-level
baseUrl is the backend's base URL and must use HTTPS.
BackendEndpoints.baseUrl is removed. Backend endpoints given as paths
resolve against baseUrl; an endpoint given as an absolute HTTPS URL is
used as it is, and http:// is refused.

The app reaches its API only through the backend's proxy, and the
backend decides where those requests go. A path starting with "/" goes
to baseUrl + proxy + the path, and a URL already under the proxy is
sent as it is rather than prefixed again. Any other absolute URL is
refused unless it is a bypass URL. Management operations build their
collection URLs on the proxy and send them through it with the session
handle and no access token: the backend attaches it, and no token is
requested from the backend.

In backend mode, getGrantedPermissions() reads the permissions from the
backend's session (BackendSession.scope, the token response's scope)
and never asks for the access token. A session without scope grants
nothing. Redirect mode is unchanged.

Signed-off-by: Brion <info@brionmario.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

parity/prs-raised Ports exist in the other SDKs, or are recorded as not applying there

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants