Skip to content

feat!: approve a relying party's session step-up without its tokens - #431

Merged
stormer78 merged 2 commits into
mainfrom
feat/step-up-approve-request
Nov 13, 2026
Merged

stormer78 merged 2 commits into
mainfrom
feat/step-up-approve-request

Conversation

@stormer78

Copy link
Copy Markdown
Contributor

Summary

The did-hosting console no longer hands the wallet a session's access and refresh tokens to step it up (affinidi/affinidi-webvh-service#445). The console opens the step-up itself with the session key its login bound, and asks the wallet only for the one thing a session key can never give: the holder's signed approval of the RP's challenge.

New page method: window.vtaWallet.approveSessionStepUp({ rpDid, approveRequest }) → { approveResponse, holderDid }.

  • approveRequest is the RP's signed auth/step-up/approve-request/0.5, exactly as the page got it back from its own auth/step-up/start/0.2: a single-use challenge bound to that session and subject, with a short TTL.
  • The background refuses the call unless rpDid is the RP DID the origin is pinned to by an approved login (pageStepUpBindingRefusal, now RP-DID-only: the wallet contacts no one for a step-up, so the base URL pin no longer applies to it).
  • The offscreen runs core's performRpStepUp: verify (proof by an enrolled executor that is rpDid, authentication purpose, addressed to and naming the active holder, not lapsed, didSigned accepted) → consent prompt on the verified reason (unchanged RUNTIME_STEP_UP_CONSENT, minus baseUrl) → sign approve-response/0.7 (assertionMethod). The page sends it and renews the session itself.
  • The wallet makes no network request for a step-up and never sees a session token.

Removed: stepUpVta (page method, runtime/offscreen messages) and core's stepUpVtaStart / stepUpVtaFinish / performStepUpVta. Per this repo's rule, no compatibility fold: the console and the wallet cut over together (the console PR never calls stepUpVta). The only consumer of stepUpVta in the workspace was the did-hosting console.

A side note on the old flow: it signed auth/refresh/0.2 with the holder's key, which the control plane refuses with auth/refresh:sessionKeyProofRequired for a session that bound a session key — the console's own renewal uses the bound key.

demo/index.html's step-up button now explains the flow instead of calling the removed method (the demo holds no session key to open a step-up with).

Behaviour changes (R3.6)

  • Page bridge contract: stepUpVta → approveSessionStepUp (different params and result). Breaking for any page calling stepUpVta.
  • A step-up is allowed for an origin pinned by a DIDComm login (no base URL), since none is used.
  • Core exports renamed as above (core CHANGELOG, BREAKING).

Tests

  • core/tests/rp-login.step-up.mjs: verifyRpStepUpRequest accepts the RP's request for this holder and refuses another subject, another recipient, a lapsed request, another enrolled executor, a page-altered request and no document; performRpStepUp shows consent the verified reason and returns an assertionMethod approve-response addressed to the RP, signs nothing on decline, and never prompts for a refused request. Existing buildStepUpApproval / verifyStepUpApproveRequest tests kept.
  • extension/tests/page-rp-binding.test.mts: step-up binding by pinned RP DID, another RP refused, DIDComm-seeded pin (no base URL) allowed, missing/non-DID rpDid refused, unpinned origin refused.
  • page-task-policy / approver-policy tests updated for the method name.

No suites were run locally; CI runs lint, build and tests.

Pre-merge checklist

  • No new reqwest::Client::new() / bare fetch(); all clients have finite timeouts (R1.2) — the step-up now makes no fetch at all.
  • No lock held across a network await (R1.3) — n/a
  • No local state committed before its remote effect, or the flow is resumable with an idempotency key (R2.1) — the wallet commits nothing; it returns a signed document.
  • Every retry is bounded + backed off; non-idempotent ops are not blind-retried (R1.4) — no retries.
  • Accept/poll/listen loops survive transient errors (R1.5) — n/a
  • Acks/deletes happen only after durable handoff (R1.6) — n/a
  • New/changed wire types: camelCase, deny_unknown_fields where security-relevant, schema registered, all consumers (incl. JS) updated (R3.*) — Trust Task documents unchanged; bridge params are camelCase; the one consumer is updated in feat(ui)!: step a wallet session up without handing the wallet its tokens, and drop the login flow visualization affinidi/affinidi-webvh-service#445.
  • Config absence = most restrictive; fail-closed if enforcement can't start (R5.*) — no pin → refused; anything but an explicit consent → nothing signed.
  • Logs/status claim only what was verified; background-job failures are surfaced (R6.*) — refusals surface to the page with the reason.
  • "Process dies on the next line" answered for every mutation touched (R2.1) — nothing is mutated; the RP's challenge lapses on its TTL.
  • Deviations from this guide flagged explicitly with rule numbers — none.

The page opens the step-up with its own session key and hands the wallet
only the relying party's signed approve-request. approveSessionStepUp
verifies it, asks for consent on the verified reason, and returns the
signed approve-response for the page to send; the wallet makes no request
to the relying party and is given no access or refresh token.

stepUpVta and core's stepUpVtaStart / stepUpVtaFinish / performStepUpVta
are replaced by approveSessionStepUp and verifyRpStepUpRequest /
performRpStepUp. A step-up is bound to the origin's pinned RP DID only.

Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
stormer78 added a commit to affinidi/affinidi-webvh-service that referenced this pull request Nov 13, 2026
…StepUp

Matches OpenVTC/vta-browser-plugin#431, and keeps it distinct from the
wallet's existing approver method approveStepUp.

Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
…nger sends

Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
@stormer78
stormer78 merged commit 637be40 into main Nov 13, 2026
4 checks passed
@stormer78
stormer78 deleted the feat/step-up-approve-request branch November 13, 2026 08:26
stormer78 added a commit to affinidi/affinidi-webvh-service that referenced this pull request Nov 13, 2026
…kens, and drop the login flow visualization (#445)

* feat(ui)!: step a wallet session up without handing the wallet its tokens, and drop the login flow visualization

The console now opens the step-up itself (auth/step-up/start, signed by the
session key), hands the wallet only the control plane's signed
approve-request, sends the wallet's signed approve-response, and renews the
session with its own session key and refresh token. The wallet bridge method
is approveStepUpRequest({ rpDid, approveRequest }); stepUpVta, which took the
access and refresh tokens, is no longer called.

The login page's 'Show flow visualization (demo)' option, its modal and the
display-only token decoding behind it are removed.

Signed-off-by: Glenn Gore <glenn.g@affinidi.com>

* refactor(ui): name the wallet's session step-up method approveSessionStepUp

Matches OpenVTC/vta-browser-plugin#431, and keeps it distinct from the
wallet's existing approver method approveStepUp.

Signed-off-by: Glenn Gore <glenn.g@affinidi.com>

---------

Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
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