Skip to content

feat: send the MetaMask auth token to built-in Infura endpoints - #9927

Open
basgys wants to merge 1 commit into
MetaMask:mainfrom
basgys:feat/network-controller-infura-auth-token
Open

feat: send the MetaMask auth token to built-in Infura endpoints#9927
basgys wants to merge 1 commit into
MetaMask:mainfrom
basgys:feat/network-controller-infura-auth-token

Conversation

@basgys

@basgys basgys commented Aug 20, 2026

Copy link
Copy Markdown

Explanation

Infura accepts the MetaMask auth token as Authorization: Bearer alongside the v3 project ID in the URL. This adds an optional getInfuraAuthToken option to NetworkController and wires it in @metamask/wallet to AuthenticationController:getBearerToken, gated on analytics opt-in.

new NetworkController({
  infuraProjectId,
  getInfuraAuthToken: async () =>
    messenger.call('AnalyticsController:getState').optedIn
      ? await messenger.call('AuthenticationController:getBearerToken')
      : undefined,
});

Built-in Infura endpoints only. The token goes on the primary endpoint of infura-type network clients, the ones built from infuraProjectId. Custom endpoints never carry it, even on an infura.io host: that is someone else's key. Failover endpoints never carry it: other providers. The gate is client type plus position in the chain, not URL matching, so rpcFailoverMode: 'forced' sends the token nowhere.

Read per request. A refreshed token is used on the next request. If the function returns undefined or throws, the request is made without the header. In the wallet that covers the locked state (getBearerToken throws until unlock) and clients that do not register AuthenticationController, such as wallet-cli.

Analytics opt-in only. The token identifies the user, which is only allowed for analytics opt-ins. The wallet returns it only while AnalyticsController state has optedIn: true, otherwise undefined. NetworkController stays policy-free: the option is the seam, and clients that construct the controller directly own the gate in their own getInfuraAuthToken.

Wallet wiring. AuthenticationController:getBearerToken is delegated to the network controller messenger, as it already is for ShieldApiService, ClaimsService, and SubscriptionService, and passed through getInfuraAuthToken. AnalyticsController:getState was already delegated. No new instance option. @metamask/profile-sync-controller is added as a dependency for the action type.

Not supported: Infura-issued JWTs. Same header, mutually exclusive. MetaMask clients use v3 keys and the UI has no JWT input.

Open question: the extension's FEATURED_RPCS networks (Linea, Base, ...) are RpcEndpointType.Custom with our project ID, so the type gate excludes them. Include them by also matching /v3/<infuraProjectId>, or keep the type gate?

References

None.

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

Note

Medium Risk
Changes outbound RPC auth on default Infura traffic and ties it to analytics opt-in and bearer retrieval; mis-gating could leak identifiers or break RPC when tokens fail.

Overview
Adds optional getInfuraAuthToken on NetworkController, threaded through auto-managed network clients into RPC fetch. A withInfuraAuthToken wrapper sets Authorization: Bearer … once per request; missing or failed token lookup still sends the RPC without the header.

The credential applies only to the primary endpoint of infura-type clients (built from infuraProjectId), not custom RPC URLs or failover endpoints—even on Infura hosts—so user keys are never paired with the MetaMask token.

@metamask/wallet wires the hook to AuthenticationController:getBearerToken when AnalyticsController reports optedIn, delegates that action on the network messenger, and adds @metamask/profile-sync-controller for the action type. Tests cover bearer behavior, per-request refresh, endpoint scoping, and wallet opt-in gating.

Reviewed by Cursor Bugbot for commit ef7260c. Bugbot is set up for automated code reviews on this repo. Configure here.

@basgys
basgys requested a review from a team as a code owner August 20, 2026 16:50
Comment thread packages/wallet/src/initialization/instances/network-controller/types.ts Outdated
@basgys basgys changed the title feat(wallet): add optional Infura auth token to network controller options feat: present the MetaMask auth token on requests to built-in Infura endpoints Aug 25, 2026
@basgys
basgys requested a review from a team as a code owner August 25, 2026 14:06
@basgys
basgys force-pushed the feat/network-controller-infura-auth-token branch 2 times, most recently from 0905232 to ad065ed Compare August 25, 2026 15:01
@basgys basgys changed the title feat: present the MetaMask auth token on requests to built-in Infura endpoints feat: send the MetaMask auth token to built-in Infura endpoints Aug 25, 2026
@basgys
basgys force-pushed the feat/network-controller-infura-auth-token branch from ad065ed to 9272c5d Compare August 25, 2026 15:17
`NetworkController` gains an optional `getInfuraAuthToken` option. It is
called once per request to the primary endpoint of a network client of type
`infura` (the ones built from `infuraProjectId`), and the token it returns is
sent as a bearer credential in the `Authorization` header alongside the v3
project ID in the URL. Custom endpoints and failover endpoints never carry the
token, so a user-supplied Infura key is never paired with it. When the token
is unavailable or retrieval throws, the request is made unauthenticated.

`@metamask/wallet` wires the option to `AuthenticationController:getBearerToken`
over the network controller messenger, and only while `AnalyticsController`
state has `optedIn: true`, since the token identifies the user.
@basgys
basgys force-pushed the feat/network-controller-infura-auth-token branch from 9272c5d to ef7260c Compare August 25, 2026 15:37
analyticsOptions: options.analyticsOptions,
// The token identifies the user, which is only allowed for analytics
// opt-ins.
getInfuraAuthToken: async () =>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thoughts on moving this inside of the NetworkController, rather than passing it in as a constructor parameter?

For context, our intention with the messenger is to use it for all communication between wallet components, reserving constructor parameters just for platform dependencies that haven't been migrated to a service yet. Eventually we hope to eliminate all (or nearly all) constructor parameters in favor of messenger actions/events.

Also, the messenger is meant to represent the full set of wallet capabilities that a particular component has. This strategy of extending the capabilities of the messenger just for initialization creates a misleading perception of the capabilities of the NetworkController (it will appear to not have the AuthenticationController:getBearerToken capability, but in reality it will), which is definitely not something we want.

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