Skip to content

feat: add DPoP core storage layer (ENG-4782)#201

Merged
mrudatsprint merged 8 commits into
parent/dpop-in-the-javascript-sdkfrom
miker/eng-4782/core-storage-layer
Jul 20, 2026
Merged

feat: add DPoP core storage layer (ENG-4782)#201
mrudatsprint merged 8 commits into
parent/dpop-in-the-javascript-sdkfrom
miker/eng-4782/core-storage-layer

Conversation

@mrudatsprint

@mrudatsprint mrudatsprint commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator

Issue:

Description:

Implement the storage for the Cryptographic Key Pair (DPoPStorage) and the storage for the tokens (DPoPTokenStore)

- Add `dpop` v2.1.1 runtime dependency and `fake-indexeddb` devDependency
  to packages/core/package.json

- SDKConfig: add optional `useDpop` and `dpopTokenStorage` fields

- UrlHelper: add `getAuthorizeUrl(state?, dpopJkt?, codeChallenge?)`
  targeting FusionAuth /oauth2/authorize directly; update UrlHelperTypes
  to include response_type, code_challenge, code_challenge_method, dpop_jkt

- DPoPStorage: IndexedDB abstraction for ES256 CryptoKeyPair persistence
  (db: fusionauth-sdk:dpop, store: keypair, keyed by clientId)

- DPoPTokenStore: localStorage/memory token storage for DPoP-bound tokens
  (key: fusionauth-sdk:tokens:<clientId>); includes getAccessToken() and
  isExpired getter

- packages/core/src/DPoP/index.ts re-exports both classes

- 54 tests passing (21 DPoPTokenStore, 6 DPoPStorage, 16 UrlHelper, 7 SDKCore,
  4 CookieHelpers)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces the core building blocks needed to support DPoP mode in @fusionauth-sdk/core, adding a persistent keypair store (IndexedDB), a token store (localStorage/memory), and an authorize URL helper for DPoP + PKCE flows.

Changes:

  • Added DPoP keypair persistence via IndexedDB (DPoPStorage) and corresponding tests.
  • Added a DPoP token storage abstraction (DPoPTokenStore) with localStorage/memory backends and tests.
  • Extended UrlHelper to generate a direct /oauth2/authorize URL for DPoP/PKCE, plus type/test updates and public exports.

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/core/src/UrlHelper/UrlHelperTypes.ts Expands query param typing to include DPoP + PKCE authorize parameters.
packages/core/src/UrlHelper/UrlHelper.ts Adds getAuthorizeUrl for direct FusionAuth /oauth2/authorize URL generation.
packages/core/src/UrlHelper/UrlHelper.test.ts Adds coverage for getAuthorizeUrl query string construction.
packages/core/src/SDKConfig/SDKConfig.ts Introduces DPoP opt-in configuration (useDpop, dpopTokenStorage).
packages/core/src/index.ts Exposes the new DPoP module from the package entrypoint.
packages/core/src/DPoP/index.ts Barrel exports for DPoP storage/token store APIs.
packages/core/src/DPoP/DPoPTokenStore.ts Implements token persistence abstraction for DPoP mode.
packages/core/src/DPoP/DPoPTokenStore.test.ts Adds tests for localStorage and memory token persistence behavior.
packages/core/src/DPoP/DPoPStorage.ts Implements IndexedDB-backed persistence for DPoP keypairs.
packages/core/src/DPoP/DPoPStorage.test.ts Adds IndexedDB persistence tests using fake-indexeddb.
packages/core/package.json Adds dpop runtime dependency and fake-indexeddb dev dependency.
AGENTS.md Adds repo agent guidance (workspace layout, testing notes, and conventions).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/core/src/DPoP/DPoPStorage.ts
Comment thread packages/core/src/SDKConfig/SDKConfig.ts
- Remove 'as any' cast in catch block — reject() accepts unknown directly
- Add tests for indexedDB unavailable (SSR/non-browser): all three public
  methods (getKeyPair, setKeyPair, clearKeyPair) reject with a descriptive error
- Add test for indexedDB.open() throwing synchronously (e.g. security policy block)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 5 comments.

Comment thread packages/core/src/DPoP/DPoPStorage.ts Outdated
Comment thread packages/core/src/DPoP/DPoPTokenStore.ts
Comment thread packages/core/src/DPoP/DPoPTokenStore.ts Outdated
Comment thread packages/core/src/SDKConfig/SDKConfig.ts
Comment thread packages/core/src/DPoP/DPoPStorage.test.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 4 comments.

Comment thread packages/core/src/DPoP/DPoPStorage.ts
Comment thread packages/core/src/DPoP/DPoPStorage.ts
Comment thread packages/core/src/SDKConfig/SDKConfig.ts
Comment thread packages/core/src/index.ts
@mrudatsprint
mrudatsprint marked this pull request as ready for review July 16, 2026 01:22
@mrudatsprint
mrudatsprint changed the base branch from miker/dpop-in-the-javascript-sdk to main July 16, 2026 01:22
@mrudatsprint
mrudatsprint changed the base branch from main to miker/dpop-in-the-javascript-sdk July 16, 2026 01:24
All three DPoPStorage methods (getKeyPair, setKeyPair, clearKeyPair) now
resolve on tx.oncomplete and reject on tx.onerror / tx.onabort.

Previously, resolving on req.onsuccess meant the caller was told 'success'
before the transaction had fully committed — a transaction abort occurring
after the request succeeded (e.g. quota exceeded) would go undetected.

Applies the same fix consistently to all three methods, including getKeyPair
(readonly, lower risk, but now consistent) and setKeyPair (readwrite, same
durability concern as clearKeyPair).

Adds a test that aborts a clearKeyPair transaction synchronously inside the
request onsuccess handler and verifies the promise rejects and the key pair
is still present in IndexedDB.
@mrudatsprint
mrudatsprint marked this pull request as draft July 16, 2026 01:58
@mrudatsprint
mrudatsprint changed the base branch from miker/dpop-in-the-javascript-sdk to main July 16, 2026 01:58
@mrudatsprint
mrudatsprint marked this pull request as ready for review July 16, 2026 14:40
@mrudatsprint
mrudatsprint requested review from a team as code owners July 16, 2026 14:40

@mrudatsprint mrudatsprint left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

self-review completed

Comment thread packages/core/src/DPoP/DPoPStorage.test.ts
Comment thread packages/core/src/DPoP/DPoPStorage.ts Outdated
Comment thread packages/core/src/SDKConfig/SDKConfig.ts
@mrudatsprint
mrudatsprint changed the base branch from main to miker/dpop-in-the-javascript-sdk July 16, 2026 15:54

@lyleschemmerling lyleschemmerling 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.

The use of the indexedDB looks good. I am still not sold that this DPoP library is worth adding the dependency. Have you looked at what it would take to do without it?

Comment thread packages/core/src/DPoP/DPoPStorage.ts Outdated
Comment thread packages/core/src/DPoP/DPoPStorage.test.ts Outdated
@mrudatsprint

Copy link
Copy Markdown
Collaborator Author

The use of the indexedDB looks good. I am still not sold that this DPoP library is worth adding the dependency. Have you looked at what it would take to do without it?

The functionality to be implemented if we didn't use the library:

  • generateKeyPair - Call to the Web Crypto API to generate a key. Web Crypto is a browser/JavaScript-runtime API and isn't a library. This appears to be minimal code.

  • calculateThumbprint

    • A little more work as the Crypto API would be used to export the key, generate a digest using the API and base64url the digest to produce the thumbprint. base64url is a variant of base64 encoding.
    • How would we validate the thumbprint? We could borrow example fixtures from panva/dpop outputs to validate.
  • generateProof

    • The majority of the work would be here where the JWT would be built.
    • Take into consideration:
      • building the header
      • building the payload, including hashing an available access token
      • encoding the header and payload
      • signing and base64url the signature

I believe this can be implemented in 1-2 days. Would we want to perform cross-validation testing against the panva/dpop library as a sanity check that we're building the thumbprint and proof correctly? Also, integration testing against FusionAuth will give us the best validation. Thus, end to end tests would be key. The RFC has an example to validate against.

…g object

- Export DEFAULT_DPOP_DB_NAME, DEFAULT_DPOP_DB_VERSION, DEFAULT_DPOP_STORE_NAME
  as named constants (no hardcoded magic strings anywhere in the codebase)

- Add DPoPStorageConfig interface with clientId (required) and optional
  dbName, dbVersion, storeName fields — each defaults to the exported constant

- Refactor DPoPStorage constructor from positional (clientId: string) to
  config object, matching the UrlHelperConfig convention in this monorepo

- openDb() and all three public methods now reference instance fields
  (this.dbName, this.dbVersion, this.storeName) instead of module constants

- Add tests: defaults apply when no config overrides provided; custom dbName
  and storeName land data in the right database; two instances with different
  dbNames but the same clientId do not share keys; dbVersion downgrade
  produces a clean rejection (VersionError)

- Update AGENTS.md: note the config-object constructor convention and the
  IndexedDB dbVersion must-only-increase constraint
@mrudatsprint
mrudatsprint merged commit f5b1143 into parent/dpop-in-the-javascript-sdk Jul 20, 2026
4 checks passed
@mrudatsprint
mrudatsprint deleted the miker/eng-4782/core-storage-layer branch July 20, 2026 19:49
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.

3 participants