Skip to content

feat(security): let a consumer supply the access token via setAccessTokenResolver - #324

Draft
gcutrini wants to merge 2 commits into
v4.xfrom
feat/access-token-resolver
Draft

feat(security): let a consumer supply the access token via setAccessTokenResolver#324
gcutrini wants to merge 2 commits into
v4.xfrom
feat/access-token-resolver

Conversation

@gcutrini

@gcutrini gcutrini commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

ref: https://app.clickup.com/t/86bbnquuq

uicore's getAccessToken assumes the access token lives on the client. It reads and refreshes the token from local storage and serializes refreshes with navigator.locks. That model does not fit a server-rendered host such as the Next.js event site, where the token is held in an encrypted httpOnly cookie and is never exposed to JavaScript. There is no client-side token for uicore to read, lock, or renew.

Keeping the token out of JavaScript is also a deliberate security property. A token that never reaches the browser's JS context cannot be stolen by XSS or replayed from client code.

Several uicore modules call getAccessToken, so the source must be injectable inside uicore rather than overridden by the consumer. Today a cookie-based host has to work around this by aliasing security/methods at build time and substituting its own implementation, which is invasive (build-system specific, replaces the whole module) and easy to drift out of sync.

What

Add setAccessTokenResolver to security/methods: a consumer registers where the access token comes from, without replacing anything.

  • It does not bypass getAccessToken. When a resolver is registered, getAccessToken delegates to it; otherwise the built-in flow is unchanged. Passing a non-function resets to the built-in.
  • It is opt-in. Existing consumers such as summit-admin are unaffected; there is no behavior change unless they call setAccessTokenResolver.

Renewal is the consumer's concern

When a resolver is registered, uicore delegates token retrieval to it and does not apply its own storage, locking, or renewal to the returned value. Freshness is left entirely to the consumer. A cookie-based host, for example, renews server-side (the server refreshes the session cookie via the refresh_token grant, and proxied API calls attach the real bearer from the cookie) and hands uicore only a "session present" placeholder, so there is nothing on the client to lock or renew.

Implementation note

The resolver is module-level state, so security/methods is also externalized in the webpack build. That way every uicore lib entry shares one methods instance and sees the registered resolver; otherwise an entry that inlines its own copy keeps its own null resolver and ignores it.

@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

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.

…okenResolver

Add setAccessTokenResolver: when a resolver is registered, getAccessToken delegates to it; otherwise the built-in flow is unchanged. Passing a non-function resets to the built-in.

The resolver is module-level state, so externalize security/methods in the webpack build so every uicore lib entry shares one instance and sees the registered resolver.
Delegates to a registered resolver, a later resolver replaces the previous one, and a non-function argument clears it.
@gcutrini
gcutrini force-pushed the feat/access-token-resolver branch from 670afb2 to 63e4c51 Compare August 24, 2026 17:42
@gcutrini
gcutrini changed the base branch from main to v4.x August 24, 2026 17:42
@smarcet

smarcet commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

@gcutrini i cant approve this , this bypass entirely the renewal flow please provide a rationale for this

@smarcet

smarcet commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

@gcutrini following up on the earlier request for a rationale: the current PR description explains why the resolver has to live inside security/methods (multiple internal call sites: security/actions.js, utils/query-actions.js, attendance-tracker.js, inputs/dropzone/index.js) and why webpack.common.js needs the externals change (multi-entry UMD build, module-level state duplicated per bundle otherwise) — both check out against the code.

What's still missing is the actual concern raised: when _resolveAccessToken is set, getAccessToken() returns _resolveAccessToken() directly (methods.js:349), bypassing the cross-tab mutex (navigator.locks/SuperTokensLock), the expiry check against ACCESS_TOKEN_SKEW_TIME, and the automatic refresh + storeAuthInfo persistence in _getAccessToken. Two things need an answer before this can be approved:

  1. What's the concrete use case that requires bypassing the entire pipeline, rather than a narrower hook that only swaps the token source while _getAccessToken still owns expiry/refresh?
  2. Once a resolver is active, who is responsible for renewal — is the consumer's resolver expected to reimplement expiry checking and refresh itself? If so, that contract should be documented (JSDoc on setAccessTokenResolver), since every module that currently benefits from automatic renewal (query-actions, attendance-tracker, dropzone, security/actions) will silently stop renewing once any part of the app calls setAccessTokenResolver.

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.

2 participants