Skip to content

Read granted permissions from the backend session in backend mode - #148

Merged
brionmario merged 1 commit into
thunder-id:mainfrom
brionmario:granted-scopes-backend-session
Oct 30, 2026
Merged

brionmario merged 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

In backend mode (endpoints.backend) the access token stays on the backend, but useGrantedPermissions reads the permissions by decoding the access token. In backend mode getAccessToken() asks the backend's token endpoint for one, which a BFF doesn't offer, since keeping tokens out of the browser is the point of the pattern (RFC 10017 §6.1). The app then sees no permissions: for example, a ThunderID admin console run behind a BFF shows "Nothing to manage".

An OAuth client learns what it was granted from the token response's scope (RFC 6749 §5.1), not by reading the access token. A backend has that, so it can report it with the session, and the app reads it from there.

Approach

  • Session contract: BackendSession gains an optional scope, the granted scopes as space-separated text, like the token response's scope.
  • Client: ThunderIDBrowserClient.getGrantedPermissions() returns the granted permissions.
    • In backend mode it reads them only from the session the client already holds. It makes no extra request, and it never asks for the access token: a session without scope grants nothing.
    • In redirect mode it reads the access token's scope or scopes claim, as useGrantedPermissions did before.
  • React and Vue: the context exposes getGrantedPermissions, and useGrantedPermissions calls it instead of decoding the token itself. The hooks' signatures and results don't change.
  • Nuxt: the plugin provides getGrantedPermissions from its existing token route, so Nuxt behaves as before.
  • Next.js: getGrantedPermissions is marked unsupported on the client context, like getAccessToken. Its own useGrantedPermissions reads permissions through a server action and is unchanged.

A backend that doesn't send scope yet makes its app see no permissions in backend mode, where before it saw them only if it also issued tokens through endpoints.backend.token.

Related Issues

  • N/A

Related PRs

SDK parity

The mobile SDKs have the same gap: each has getGrantedPermissions() and a backend mode, and in backend mode each reads permissions by decoding a token from the backend's token endpoint. Ported with the same change (scope on the backend session, read in backend mode, never a token request):

Checklist

  • Followed the contribution guidelines.
  • Manual test round performed and verified.
    • Not yet run in a browser. The BFF side was checked: its session endpoint returns scope for the product's token.
  • Documentation provided. (Add links if there are any)
  • Tests provided. (Add links if there are any)
    • Unit Tests (ThunderIDBrowserClient.backend.test.ts, useGrantedPermissions.test.tsx, resource-query.test.ts)
    • 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.

In backend mode the browser never asks for an access token to read permissions. The permissions decide only what the app shows; the server still checks every request.

In backend mode the access token stays on the backend, but
useGrantedPermissions read the permissions by decoding the access
token. In backend mode that meant asking the backend's token endpoint
for one, which a BFF doesn't offer, so an app saw no permissions.

The backend's session now carries the granted scopes (BackendSession
scope, the token response's scope), and a new
ThunderIDBrowserClient.getGrantedPermissions() reads them from the
session the client already holds. In backend mode a session without
scope grants nothing, and the client never asks for a token. In
redirect mode the permissions are still read from the access token.

The React and Vue useGrantedPermissions call getGrantedPermissions()
through their contexts. Nuxt provides it from its existing token
route, so it behaves as before, and Next.js marks it unsupported on
its client context, since its hook reads permissions through a server
action.

Signed-off-by: Brion <info@brionmario.com>
@brionmario brionmario added the parity/prs-not-needed Nothing here reaches another SDK 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.

@brionmario
brionmario merged commit 9cce217 into thunder-id:main Oct 30, 2026
8 of 9 checks passed
@brionmario brionmario changed the title Read granted permissions from the backend session in backend mode Read granted permissions from the backend session in BFF mode Oct 30, 2026
@brionmario brionmario added parity/prs-raised Ports exist in the other SDKs, or are recorded as not applying there and removed parity/prs-not-needed Nothing here reaches another SDK labels Oct 30, 2026
@brionmario brionmario changed the title Read granted permissions from the backend session in BFF mode BFF: baseUrl is the backend, and permissions come from the session Oct 30, 2026
@brionmario brionmario changed the title BFF: baseUrl is the backend, and permissions come from the session Read granted permissions from the backend session in backend mode Oct 30, 2026
brionmario added a commit to brionmario/thunderid that referenced this pull request Oct 30, 2026
Moves @thunderid/react to 1.11.2 and @thunderid/react-router to 1.10.2,
which bring @thunderid/browser 1.9.2 and @thunderid/javascript 1.10.2.

These include thunder-id/javascript-sdks#148: in backend mode,
useGrantedPermissions reads the granted permissions from the backend's
session (scope) instead of decoding an access token. A console signed
in through a backend (BFF) then shows the pages its permissions allow,
instead of "Nothing to manage".

Signed-off-by: Brion <info@brionmario.com>
lashinijay pushed a commit to lashinijay/thunderid that referenced this pull request Oct 30, 2026
Moves @thunderid/react to 1.11.1 and @thunderid/react-router to 1.10.1
(@thunderid/browser 1.9.1 and @thunderid/javascript 1.10.1 follow
transitively). These releases post the SSO sessions a flow's responses
return on the authorization callback (thunder-id/javascript-sdks#147), so a
sign-in through the Gate that asks for the device_sso scope now receives a
device secret (thunder-id#6227). They also bring Device Grant support
(thunder-id/javascript-sdks#140), an ouId prop on GroupMembers
(thunder-id/javascript-sdks#146), and granted permissions read from the
backend session in backend mode (thunder-id/javascript-sdks#148).

Refs thunder-id#6144

Signed-off-by: ImalshaD <plid475@gmail.com>
brionmario added a commit to brionmario/thunderid that referenced this pull request Oct 30, 2026
Moves @thunderid/react to 1.11.2 and @thunderid/react-router to 1.10.2,
which bring @thunderid/browser 1.9.2 and @thunderid/javascript 1.10.2.

They include two backend-mode (endpoints.backend) changes:

- thunder-id/javascript-sdks#148: useGrantedPermissions reads the
  granted permissions from the backend's session (scope) instead of
  decoding an access token. A console signed in through a backend (BFF)
  then shows the pages its permissions allow, instead of "Nothing to
  manage".
- thunder-id/javascript-sdks#149: the top-level baseUrl is the backend,
  endpoint paths resolve against it, and the API behind the proxy is
  endpoints.backend.apiBaseUrl.

Neither changes redirect mode, which the consoles use by default.

Signed-off-by: Brion <info@brionmario.com>
brionmario added a commit to brionmario/thunderid that referenced this pull request Oct 30, 2026
Moves @thunderid/react to 1.11.3 and @thunderid/react-router to 1.10.3,
which bring @thunderid/browser 1.9.3 and @thunderid/javascript 1.10.3.

They include three backend-mode (endpoints.backend) changes:

- thunder-id/javascript-sdks#148: useGrantedPermissions reads the
  granted permissions from the backend's session (scope) instead of
  decoding an access token. A console signed in through a backend (BFF)
  then shows the pages its permissions allow, instead of "Nothing to
  manage".
- thunder-id/javascript-sdks#149: the top-level baseUrl is the backend,
  and endpoint paths resolve against it.
- thunder-id/javascript-sdks#150: the SDK's API calls go to the
  backend's proxy (baseUrl + proxy), so the frontend never needs the
  API's own address; the backend maps the proxy to it.

None of them changes redirect mode, which the consoles use by default.

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