sdk: OAuth 2.0 PKCE rewrite with React Native support#13924
sdk: OAuth 2.0 PKCE rewrite with React Native support#13924rickyrombo merged 24 commits intomainfrom
Conversation
🦋 Changeset detectedLatest commit: 11a62d7 The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
🌐 Web preview readyPreview URL: https://audius-web-preview-pr-13924.audius.workers.dev Unique preview for this PR (deployed from this branch). |
|
No dependency changes detected. Learn more about Socket for GitHub. 👍 No dependency changes detected in pull request |
There was a problem hiding this comment.
Pull request overview
This PR rewrites the SDK’s OAuth implementation to use OAuth 2.0 Authorization Code Flow with PKCE, adds a token-store abstraction with web + React Native defaults, and updates docs/build wiring to support React Native/Expo.
Changes:
- Replace legacy implicit OAuth flow APIs with PKCE-based
login()/handleRedirect()and async token-store-backed auth state. - Introduce
OAuthTokenStoreinterface plusTokenStoreMemory,TokenStoreLocalStorage, andTokenStoreAsyncStorageimplementations with updated tests. - Add React Native entrypoint (
index.native.ts) and update docs/changeset/build config accordingly.
Reviewed changes
Copilot reviewed 30 out of 31 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/sdk/src/sdk/utils/oauthScope.ts | Removes write_once param validation helper; scope validation now read/write only. |
| packages/sdk/src/sdk/types.ts | Adds tokenStore and openUrl to services; adds redirectUri to SDK config schemas. |
| packages/sdk/src/sdk/sdkBrowserDist.ts | Switches global browser dist export to use createSdk. |
| packages/sdk/src/sdk/sdk.ts | Removes runtime sdk export; keeps AudiusSdk type based on createSdk. |
| packages/sdk/src/sdk/oauth/types.ts | Restricts OAuth scopes to read/write and removes legacy result/params types. |
| packages/sdk/src/sdk/oauth/tokenStoreLocalStorage.test.ts | Adds tests for localStorage token persistence. |
| packages/sdk/src/sdk/oauth/tokenStore.ts | Replaces concrete token store class with OAuthTokenStore async interface. |
| packages/sdk/src/sdk/oauth/tokenStore.test.ts | Updates tests to cover new memory token store behavior. |
| packages/sdk/src/sdk/oauth/pkce.ts | Reworks PKCE helpers to use noble SHA-256 and adds generateState(). |
| packages/sdk/src/sdk/oauth/index.ts | Updates OAuth exports (no longer re-exporting pkce helpers). |
| packages/sdk/src/sdk/oauth/TokenStoreMemory.ts | Adds in-memory token store implementation. |
| packages/sdk/src/sdk/oauth/TokenStoreLocalStorage.ts | Adds localStorage-backed token store implementation. |
| packages/sdk/src/sdk/oauth/TokenStoreAsyncStorage.ts | Adds AsyncStorage-backed token store for React Native. |
| packages/sdk/src/sdk/oauth/OAuth.ts | Major OAuth rewrite: async login(), handleRedirect(), token exchange + refresh + logout. |
| packages/sdk/src/sdk/oauth/OAuth.test.ts | Expands/updates tests for new OAuth behavior and API surface. |
| packages/sdk/src/sdk/middleware/addTokenRefreshMiddleware.ts | Updates refresh middleware to await async hasRefreshToken(). |
| packages/sdk/src/sdk/middleware/addTokenRefreshMiddleware.test.ts | Updates middleware tests for async hasRefreshToken(). |
| packages/sdk/src/sdk/index.ts | SDK entry exports updated; sdk alias export removed here. |
| packages/sdk/src/sdk/createSdkWithServices.ts | Updates service initialization + token store wiring; OAuth is always constructed. |
| packages/sdk/src/sdk/createSdk.ts | Wires redirectUri/openUrl into OAuth; uses async token store for Configuration.accessToken. |
| packages/sdk/src/index.ts | Adds top-level sdk alias export (sdk = createSdk). |
| packages/sdk/src/index.native.ts | Adds React Native entrypoint with default AsyncStorage + expo-web-browser login handling. |
| packages/sdk/rollup.config.ts | Updates React Native build input to src/index.native.ts. |
| packages/sdk/package.json | Adds RN peer deps (async-storage, expo-web-browser) and dev dep for building. |
| packages/protocol-dashboard/package.json | Pins @audius/sdk and updates legacy SDK version. |
| packages/distro/src/components/DistributorCard.tsx | Removes deprecated oauth.init() usage. |
| package-lock.json | Locks new dependencies/versions for updated packages. |
| docs/docs/pages/sdk/oauth.mdx | Rewrites OAuth documentation for PKCE + web/mobile flows + new APIs. |
| .changeset/tricky-zoos-film.md | Removes obsolete changeset. |
| .changeset/angry-knives-fold.md | Removes obsolete changeset. |
| .changeset/oauth-pkce-breaking-changes.md | Adds major-version changeset documenting breaking changes and migration guide. |
Comments suppressed due to low confidence (1)
packages/sdk/src/sdk/index.ts:7
sdkis no longer exported from this entrypoint, but the Rollup browser builds usesrc/sdk/index.tsas their input (sdkBrowserConfigCjs/sdkBrowserConfigEsinpackages/sdk/rollup.config.ts). That means browser consumers importing{ sdk }from@audius/sdkwill lose the alias in browser bundles even thoughsrc/index.tsreintroduces it. Consider re-exportingsdkfrom here (e.g.export { createSdk as sdk }) or switching the browser build inputs tosrc/index.tsso the alias is consistently available across environments.
/* eslint-disable import/export */
export { createSdk } from './createSdk'
export {
createSdkWithServices,
type AudiusSdkWithServices
} from './createSdkWithServices'
export type { AudiusSdk } from './sdk'
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| }, | ||
| "peerDependencies": { | ||
| "@solana/web3.js": "^1.95.8" | ||
| "@react-native-async-storage/async-storage": ">=1.0.0", |
There was a problem hiding this comment.
can/should these be pinned?
There was a problem hiding this comment.
no idts, that's too restrictive for peer deps imo
…tton(), make login() async and return void, make getRedirectResult() return void, make basePath required
…N out-of-box, always define oauth
Removes the `typeof window !== 'undefined'` guard that made `oauth` typed as `OAuth | undefined`, matching the fix already applied in `createSdk.ts`. This resolves type errors in web where callers expect `oauth: OAuth`. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces scattered _settleLogin() early-returns in login() with a top-level try/catch so the lock is guaranteed to release on any unexpected error (e.g. sessionStorage quota exceeded). Adds a test covering lock release after an unexpected setup failure. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move TokenStoreAsyncStorage instantiation inside sdk() in index.native.ts - Add comment explaining the let/definite-assignment pattern for sdkInstance - Distinguish expo-web-browser 'locked' result from user cancellation - Make _clearPopupCheckInterval private - Add JSDoc note that hasRedirectResult() returns false after handleRedirect() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… Native - Dynamic import expo-web-browser inside defaultOpenUrl so the module is only loaded when login() is called, not at SDK initialization - Add peer dependency install instructions to the OAuth docs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…browser - Settle login promise with error when server redirects back with an error param (e.g. access_denied on cancel) so callers aren't left hanging - Switch dynamic import() to require() for expo-web-browser so Metro can resolve the module statically at bundle time Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The browser CJS and ESM rollup configs were using src/sdk/index.ts as their entry point, which omitted the `sdk` alias added in src/index.ts. Switch to src/index.ts to match the non-browser configs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
b8e49aa to
70c59c0
Compare
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Summary
sdk.oauthto use OAuth 2.0 Authorization Code Flow with PKCE — no backend or client secret requiredinit(),renderButton(),write_oncescope, and associated callback typeslogin()is nowasyncand returnsPromise<void>; errors surface via rejection instead of callbacksredirectUrican be set once insdk({ redirectUri: '...' })config and optionally overridden perlogin()callsdk.oauthis always defined — no null-check or!assertion neededAsyncStoragetoken persistence +expo-web-browserOAuth session, with the redirect handled automatically insidelogin()handleRedirect(),isAuthenticated(),getUser(),refreshAccessToken(),logout()401responses and retries with a fresh tokenTest plan
login()resolves after user approves,getUser()returns profilehandleRedirect()completes exchange,getUser()returns profilelogin()rejects with errorisAuthenticated()returnstrueafter login,falseafterlogout()refreshAccessToken()returns a new token; auto-refresh fires on 401login()opens in-app browser and resolves after redirect🤖 Generated with Claude Code