Skip to content

feat(dpop): attach DPoP proof to token request and handle nonce retry#2511

Open
abhip2565 wants to merge 2 commits into
inji:developfrom
tw-mosip:feat/dpop-token-request
Open

feat(dpop): attach DPoP proof to token request and handle nonce retry#2511
abhip2565 wants to merge 2 commits into
inji:developfrom
tw-mosip:feat/dpop-token-request

Conversation

@abhip2565

@abhip2565 abhip2565 commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Wires the wallet side of DPoP (RFC 9449) per the DPoP ADR.

The VCIClient library owns DPoP end-to-end: it builds and signs the token-endpoint proof and delivers it on tokenRequest.dpopProof. The wallet's only responsibilities are to copy that proof into the DPoP header on the token POST and, on an authorization server use_dpop_nonce challenge, ask the library for a fresh proof bound to the supplied nonce and retry once.

Changes:

  • Extract the token request into shared/openId4VCI/sendTokenRequest.ts with DPoP header support and the use_dpop_nonce retry, preserving the existing VciClientErrorResponse error shape.
  • Add VciClient.generateTokenDPoPProof(dpopNonce) and expose it through the Android (InjiVciClientModule / VCIClientBridge) and iOS (RNVCIClientModule) native bridges; forward tokenRequest.dpopProof to JS.
  • Jest tests for the DPoP header attachment, nonce retry, and error handling.

Note

The native bridge changes consume TokenRequest.dpopProof and VCIClient.generateTokenDPoPProof, which are introduced by the DPoP changes in inji-vci-client (inji/inji-vci-client#145) and inji-vci-client-ios-swift (inji/inji-vci-client-ios-swift#107). The native build needs those published and the dependency bumped; the JS layer (and its tests) are self-contained and gracefully no-op when dpopProof is absent.

Summary by CodeRabbit

  • New Features

    • Added DPoP proof generation support for token requests across mobile and shared client layers.
    • Token requests now include DPoP proof details when available.
    • Added support for retrying token requests after a DPoP nonce challenge.
  • Bug Fixes

    • Improved handling of token endpoint errors with clearer fallback behavior on failed retries.
    • Added validation for missing token endpoint input before making a request.
  • Tests

    • Added coverage for DPoP header behavior, nonce retry flow, proxy endpoint usage, and error handling.

Wire the wallet side of DPoP (RFC 9449) per the DPoP ADR. The VCIClient
library builds and signs the token-endpoint proof and delivers it on
tokenRequest.dpopProof; the wallet copies it into the DPoP header on the
token POST. On an authorization server use_dpop_nonce challenge the wallet
asks the library for a fresh proof bound to the nonce and retries once.

- Extract the token request into shared/openId4VCI/sendTokenRequest with
  DPoP header support and the use_dpop_nonce retry.
- Add VciClient.generateTokenDPoPProof and expose it through the Android and
  iOS native bridges; forward tokenRequest.dpopProof to JS.
- Unit tests for the DPoP header, nonce retry, and error handling.

Signed-off-by: abhip2565 <paul.apaul.abhishek.ap@gmail.com>
@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@abhip2565, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 57 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 49936c0c-fa03-46d0-8735-1cf9a06b2fe8

📥 Commits

Reviewing files that changed from the base of the PR and between bfba3b3 and 3c99857.

📒 Files selected for processing (2)
  • shared/constants.ts
  • shared/openId4VCI/sendTokenRequest.ts

Walkthrough

Adds generateTokenDPoPProof to Android and iOS native bridges and the VciClient TypeScript class. Extracts token request logic from IssuersService into a shared sendTokenRequest module that includes DPoP header injection and a use_dpop_nonce challenge retry mechanism. Propagates dpopProof in token request callback payloads on both platforms.

DPoP Token Request Flow

Layer / File(s) Summary
Native bridge: generateTokenDPoPProof and dpopProof propagation
shared/vciClient/VciClient.ts, android/.../InjiVciClientModule.java, android/.../VCIClientBridge.kt, ios/RNVCIClientModule.m, ios/RNVCIClientModule.swift
Adds generateTokenDPoPProof method to the VciClient TS class and both Android and iOS React Native bridges; extends token request callback payloads to include dpopProof.
Shared sendTokenRequest with DPoP retry logic
shared/openId4VCI/sendTokenRequest.ts
New module builds form-encoded token request bodies, conditionally sets the DPoP header, detects use_dpop_nonce (HTTP 400) challenges, generates a refreshed proof via VciClient, and retries once before throwing a mapped VciClientErrorResponse.
IssuersService refactored to use shared sendTokenRequest
machines/Issuers/IssuersService.ts
Removes the inline sendTokenRequest implementation and switches to the imported shared helper; drops the VciClientErrorResponse named import from VciClient.
sendTokenRequest test suite
shared/openId4VCI/sendTokenRequest.test.ts
Covers DPoP header presence/absence, nonce-challenge retry success and failure, non-retryable errors, proxy endpoint override, and missing tokenEndpoint validation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Suggested reviewers

  • swatigoel

Poem

🐇 Hippity-hop, a token flies,
With DPoP headers to the skies!
A nonce arrives — no need to fret,
We retry once and never sweat.
Shared helpers now keep code so neat,
This rabbit's proof is hard to beat! 🔑

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main DPoP token-request and nonce-retry changes.
Description check ✅ Passed The description covers the core changes and notes dependency constraints, though it omits the issue link and screenshots sections.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
shared/openId4VCI/sendTokenRequest.test.ts (1)

83-133: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a nonce-path test for proof-generation failures.

These cases only cover the branch where generateTokenDPoPProof() succeeds. That call is the new native-bridge dependency in this flow, so a rejection there would currently bypass this suite entirely.

Suggested test
+  it('surfaces proof generation failures during a use_dpop_nonce retry', async () => {
+    (global.fetch as jest.Mock).mockResolvedValueOnce(
+      fakeResponse({
+        ok: false,
+        status: 400,
+        body: '{"error":"use_dpop_nonce"}',
+        nonce: 'server-nonce',
+      }),
+    );
+    mockGenerateTokenDPoPProof.mockRejectedValueOnce(new Error('bridge failed'));
+
+    await expect(
+      sendTokenRequest({...baseRequest, dpopProof: 'proof-a'}),
+    ).rejects.toThrow('bridge failed');
+    expect((global.fetch as jest.Mock).mock.calls).toHaveLength(1);
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@shared/openId4VCI/sendTokenRequest.test.ts` around lines 83 - 133, The nonce
retry coverage in sendTokenRequest.test.ts only verifies the success path after
a use_dpop_nonce challenge, so add a test for when generateTokenDPoPProof()
fails while handling that nonce challenge. Extend the sendTokenRequest retry
branch tests around sendTokenRequest and mockGenerateTokenDPoPProof to reject on
the retry attempt, and assert the request fails with the expected issuer error
handling instead of proceeding to a second fetch.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@shared/openId4VCI/sendTokenRequest.ts`:
- Around line 58-63: `toVciClientError()` is dropping the existing error fields
that `VciClient.ts` still expects. Update the VCI error mapping in
`sendTokenRequest()` so the returned `VciClientErrorResponse` preserves the
original `code` and `message` contract while also supporting `issuerErrorCode`
and `issuerErrorMessage`. Make the change in the `toVciClientError` helper and
ensure `VciClient` continues to receive the same thrown error shape it already
handles.

---

Nitpick comments:
In `@shared/openId4VCI/sendTokenRequest.test.ts`:
- Around line 83-133: The nonce retry coverage in sendTokenRequest.test.ts only
verifies the success path after a use_dpop_nonce challenge, so add a test for
when generateTokenDPoPProof() fails while handling that nonce challenge. Extend
the sendTokenRequest retry branch tests around sendTokenRequest and
mockGenerateTokenDPoPProof to reject on the retry attempt, and assert the
request fails with the expected issuer error handling instead of proceeding to a
second fetch.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2b6ea7af-2cf6-4d80-8635-81ccce9fed50

📥 Commits

Reviewing files that changed from the base of the PR and between e4e5e2a and bfba3b3.

📒 Files selected for processing (8)
  • android/app/src/main/java/io/mosip/residentapp/InjiVciClientModule.java
  • android/app/src/main/java/io/mosip/residentapp/VCIClientBridge.kt
  • ios/RNVCIClientModule.m
  • ios/RNVCIClientModule.swift
  • machines/Issuers/IssuersService.ts
  • shared/openId4VCI/sendTokenRequest.test.ts
  • shared/openId4VCI/sendTokenRequest.ts
  • shared/vciClient/VciClient.ts

Comment on lines +58 to +63
function toVciClientError(errorText: string): VciClientErrorResponse {
const parsedError = parseError(errorText);
return {
issuerErrorCode: parsedError.error ?? 'UNKNOWN_ERROR',
issuerErrorMessage: parsedError.error_description,
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep code and message in the thrown error contract.

toVciClientError() now returns only issuerErrorCode / issuerErrorMessage, but shared/vciClient/VciClient.ts Lines 182-187 still treat VciClientErrorResponse as carrying code and message too. That changes the error shape for sendTokenRequest() callers and breaks the PR’s “preserve existing error shape” goal.

Suggested fix
 function toVciClientError(errorText: string): VciClientErrorResponse {
   const parsedError = parseError(errorText);
+  const code = parsedError.error ?? 'UNKNOWN_ERROR';
+  const message =
+    parsedError.error_description ?? 'An unknown error occurred';
+
   return {
-    issuerErrorCode: parsedError.error ?? 'UNKNOWN_ERROR',
-    issuerErrorMessage: parsedError.error_description,
+    code,
+    message,
+    issuerErrorCode: code,
+    issuerErrorMessage: parsedError.error_description,
   };
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function toVciClientError(errorText: string): VciClientErrorResponse {
const parsedError = parseError(errorText);
return {
issuerErrorCode: parsedError.error ?? 'UNKNOWN_ERROR',
issuerErrorMessage: parsedError.error_description,
};
function toVciClientError(errorText: string): VciClientErrorResponse {
const parsedError = parseError(errorText);
const code = parsedError.error ?? 'UNKNOWN_ERROR';
const message =
parsedError.error_description ?? 'An unknown error occurred';
return {
code,
message,
issuerErrorCode: code,
issuerErrorMessage: parsedError.error_description,
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@shared/openId4VCI/sendTokenRequest.ts` around lines 58 - 63,
`toVciClientError()` is dropping the existing error fields that `VciClient.ts`
still expects. Update the VCI error mapping in `sendTokenRequest()` so the
returned `VciClientErrorResponse` preserves the original `code` and `message`
contract while also supporting `issuerErrorCode` and `issuerErrorMessage`. Make
the change in the `toVciClientError` helper and ensure `VciClient` continues to
receive the same thrown error shape it already handles.

Signed-off-by: abhip2565 <paul.apaul.abhishek.ap@gmail.com>
@abhip2565 abhip2565 force-pushed the feat/dpop-token-request branch 2 times, most recently from e1f75bb to 3c99857 Compare June 30, 2026 06:06
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