Skip to content

feat: persist access token and metadata access token in seedless controller #6060

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jul 15, 2025

Conversation

himanshuchawla009
Copy link
Contributor

@himanshuchawla009 himanshuchawla009 commented Jul 2, 2025

Explanation

  • This update introduces two new tokens, accessToken and metadataAccessToken, to the SeedlessOnboardingController.

  • These tokens are essential for pairing with the profile sync auth service and accessing the metadata service before the vault is created or unlocked.

  • The changes include updates to the controller state, methods for handling these tokens, and corresponding tests to ensure proper functionality and error handling when tokens are missing.

  • Additionally, the vault data structure has been modified to include the new accessToken. Tests have been added to verify the correct behavior of the controller with respect to these tokens.

  • Removed unused nodeAuthTokens from vault.

  • Toprf sdk updated to use metadataAccessToken

References

Changelog

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, highlighting breaking changes as necessary
  • I've prepared draft pull requests for clients and consumer packages to resolve any breaking changes

@himanshuchawla009 himanshuchawla009 force-pushed the feat/persist-access-token branch from 828ed0b to 473382e Compare July 2, 2025 16:04
@himanshuchawla009 himanshuchawla009 marked this pull request as ready for review July 2, 2025 16:21
@himanshuchawla009 himanshuchawla009 requested review from a team as code owners July 2, 2025 16:21
Copy link
Contributor

@matthiasgeihs matthiasgeihs left a comment

Choose a reason for hiding this comment

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

Access token and Metadata access token are being added but not used anywhere.
I'm assuming they will be used in a future version of the toprf client.
I think the purpose of this PR would be clearer if we first update the client and then add the new tokens. (This way we can make sure that the integration is right.)

Comment on lines 246 to 247
accessToken?: string;
metadataAccessToken?: string;
Copy link
Contributor

Choose a reason for hiding this comment

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

why is it optional here but then in many/most/all? places we require this to be defined?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed here: 62be127

Copy link
Contributor

Choose a reason for hiding this comment

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

this is a breaking change then.
how would this change be reflected in the frontend code? where would the new tokens be retrieved from?

it is usually required to create a draft PR in at least one of the frontend repositories (extension/mobile) to ensure a proper integration is possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nothing will break even if you don't pass these tokens, so its technically not a breaking change.

Copy link
Contributor

Choose a reason for hiding this comment

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

if you add a required parameter to an existing API, that is a breaking change!?

@himanshuchawla009
Copy link
Contributor Author

Access token and Metadata access token are being added but not used anywhere. I'm assuming they will be used in a future version of the toprf client. I think the purpose of this PR would be clearer if we first update the client and then add the new tokens. (This way we can make sure that the integration is right.)

Yes that's correct, access_token will be used by profile sync team in auth controller. Metadata access token will be used by sdk.

@matthiasgeihs
Copy link
Contributor

Access token and Metadata access token are being added but not used anywhere. I'm assuming they will be used in a future version of the toprf client. I think the purpose of this PR would be clearer if we first update the client and then add the new tokens. (This way we can make sure that the integration is right.)

Yes that's correct, access_token will be used by profile sync team in auth controller. Metadata access token will be used by sdk.

I think a draft PR in extension would help to understand how this change is integrated.

I am thinking that we should release it as a separate version to unblock the merging of the other seedless onboarding PRs that rely on controller v2 and the store keyring key functionality.

cursor bot pushed a commit that referenced this pull request Jul 8, 2025
- Modify AuthenticationController to get accessToken from SeedlessOnboardingController state
- Add pairing logic that runs non-blocking during signIn
- Add state tracking for pairing status to prevent duplicate attempts
- Add PAIR_SOCIAL_IDENTIFIER endpoint for social pairing API
- Ensure pairing failures don't affect other authentication flows

Based on PR #6048 but using accessToken from SeedlessOnboardingController (PR #6060)
instead of injecting a separate socialPairingToken.
mirceanis
mirceanis previously approved these changes Jul 8, 2025
Copy link
Contributor

@mirceanis mirceanis left a comment

Choose a reason for hiding this comment

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

This looks good to me.
This PR is a prerequisite for the backup & sync pairing.
But @matthiasgeihs is right. For proper integration testing we do also require equivalent PRs in the clients

cursor[bot]

This comment was marked as outdated.

@himanshuchawla009
Copy link
Contributor Author

@metamaskbot publish-preview

Copy link

socket-security bot commented Jul 14, 2025

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​types/​json-stable-stringify-without-jsonify@​1.0.2991008376100
Updated@​metamask/​toprf-secure-backup@​0.4.0 ⏵ 0.6.096 +1710086 +196 +1100

View full report

cursor[bot]

This comment was marked as outdated.

cursor[bot]

This comment was marked as outdated.

@lwin-kyaw
Copy link
Contributor

@metamaskbot publish-preview

cursor[bot]

This comment was marked as outdated.

@lwin-kyaw
Copy link
Contributor

@metamaskbot publish-preview

cursor[bot]

This comment was marked as outdated.

Copy link
Contributor

Preview builds have been published. See these instructions for more information about preview builds.

Expand for full list of packages and versions.
{
  "@metamask-previews/account-tree-controller": "0.4.0-preview-c10fdda1",
  "@metamask-previews/accounts-controller": "31.0.0-preview-c10fdda1",
  "@metamask-previews/address-book-controller": "6.1.1-preview-c10fdda1",
  "@metamask-previews/announcement-controller": "7.0.3-preview-c10fdda1",
  "@metamask-previews/app-metadata-controller": "1.0.0-preview-c10fdda1",
  "@metamask-previews/approval-controller": "7.1.3-preview-c10fdda1",
  "@metamask-previews/assets-controllers": "71.0.0-preview-c10fdda1",
  "@metamask-previews/base-controller": "8.0.1-preview-c10fdda1",
  "@metamask-previews/bridge-controller": "35.0.0-preview-c10fdda1",
  "@metamask-previews/bridge-status-controller": "35.0.0-preview-c10fdda1",
  "@metamask-previews/build-utils": "3.0.3-preview-c10fdda1",
  "@metamask-previews/chain-agnostic-permission": "1.0.0-preview-c10fdda1",
  "@metamask-previews/composable-controller": "11.0.0-preview-c10fdda1",
  "@metamask-previews/controller-utils": "11.11.0-preview-c10fdda1",
  "@metamask-previews/delegation-controller": "0.5.0-preview-c10fdda1",
  "@metamask-previews/earn-controller": "2.0.1-preview-c10fdda1",
  "@metamask-previews/eip1193-permission-middleware": "1.0.0-preview-c10fdda1",
  "@metamask-previews/ens-controller": "17.0.1-preview-c10fdda1",
  "@metamask-previews/error-reporting-service": "2.0.0-preview-c10fdda1",
  "@metamask-previews/eth-json-rpc-provider": "4.1.8-preview-c10fdda1",
  "@metamask-previews/foundryup": "1.0.0-preview-c10fdda1",
  "@metamask-previews/gas-fee-controller": "24.0.0-preview-c10fdda1",
  "@metamask-previews/json-rpc-engine": "10.0.3-preview-c10fdda1",
  "@metamask-previews/json-rpc-middleware-stream": "8.0.7-preview-c10fdda1",
  "@metamask-previews/keyring-controller": "22.1.0-preview-c10fdda1",
  "@metamask-previews/logging-controller": "6.0.4-preview-c10fdda1",
  "@metamask-previews/message-manager": "12.0.2-preview-c10fdda1",
  "@metamask-previews/multichain-api-middleware": "1.0.0-preview-c10fdda1",
  "@metamask-previews/multichain-network-controller": "0.9.0-preview-c10fdda1",
  "@metamask-previews/multichain-transactions-controller": "3.0.0-preview-c10fdda1",
  "@metamask-previews/name-controller": "8.0.3-preview-c10fdda1",
  "@metamask-previews/network-controller": "24.0.0-preview-c10fdda1",
  "@metamask-previews/notification-services-controller": "14.0.0-preview-c10fdda1",
  "@metamask-previews/permission-controller": "11.0.6-preview-c10fdda1",
  "@metamask-previews/permission-log-controller": "3.0.3-preview-c10fdda1",
  "@metamask-previews/phishing-controller": "13.0.0-preview-c10fdda1",
  "@metamask-previews/polling-controller": "14.0.0-preview-c10fdda1",
  "@metamask-previews/preferences-controller": "18.4.1-preview-c10fdda1",
  "@metamask-previews/profile-sync-controller": "21.0.0-preview-c10fdda1",
  "@metamask-previews/rate-limit-controller": "6.0.3-preview-c10fdda1",
  "@metamask-previews/remote-feature-flag-controller": "1.6.0-preview-c10fdda1",
  "@metamask-previews/sample-controllers": "1.0.0-preview-c10fdda1",
  "@metamask-previews/seedless-onboarding-controller": "2.0.1-preview-c10fdda1",
  "@metamask-previews/selected-network-controller": "23.0.0-preview-c10fdda1",
  "@metamask-previews/signature-controller": "31.0.1-preview-c10fdda1",
  "@metamask-previews/token-search-discovery-controller": "3.3.0-preview-c10fdda1",
  "@metamask-previews/transaction-controller": "58.1.1-preview-c10fdda1",
  "@metamask-previews/user-operation-controller": "37.0.0-preview-c10fdda1"
}

cursor[bot]

This comment was marked as outdated.

@himanshuchawla009
Copy link
Contributor Author

Bug: Token Handling and Error Management Issues

Bug: Vault Data Incompatibility Issue

Bug: Inconsistent Token Validation Causes Errors

Was this report helpful? Give feedback by reacting with 👍 or 👎

  1. This is not issue , as token refresh token api always returns the access and metadata but the way tokens being handled currently are not optimal. We need another PR to fix the optional usage of tokens.

  2. Again not a issue, validation is only done on non vault items in this function and access tokens stays in vault.

  3. Same comments as 1, we need refactor in the way tokens are being handled in state to reduce the conflicting cases which is not the purpose of this PR.

@himanshuchawla009 himanshuchawla009 enabled auto-merge (squash) July 15, 2025 06:36
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Bug: Token Refresh Causes Infinite Recursion

The fetchMetadataAccessCreds method causes infinite recursion: an expired token triggers refreshAuthTokens, which calls authenticate, potentially leading to TOPRF operations that re-trigger fetchMetadataAccessCreds via the TOPRF client.

Additionally, accessToken and metadataAccessToken are handled unsafely. In fetchMetadataAccessCreds, newMetadataAccessToken is retrieved from state after refresh and unsafely cast as string without validation, potentially returning undefined as a string. In refreshAuthTokens, these tokens from the #refreshJWTToken result are passed directly to authenticate without validation, risking undefined or null values.

packages/seedless-onboarding-controller/src/SeedlessOnboardingController.ts#L219-L245

async fetchMetadataAccessCreds(): Promise<{
metadataAccessToken: string;
}> {
const { metadataAccessToken } = this.state;
if (!metadataAccessToken) {
throw new Error(
SeedlessOnboardingControllerErrorMessage.InvalidMetadataAccessToken,
);
}
// Check if token is expired and refresh if needed
const decodedToken = decodeJWTToken(metadataAccessToken);
if (decodedToken.exp < Math.floor(Date.now() / 1000)) {
// Token is expired, refresh it
await this.refreshAuthTokens();
// Get the new token after refresh
const { metadataAccessToken: newMetadataAccessToken } = this.state;
return {
metadataAccessToken: newMetadataAccessToken as string,
};
}
return { metadataAccessToken };
}

packages/seedless-onboarding-controller/src/SeedlessOnboardingController.ts#L1707-L1719

});
const { idTokens, accessToken, metadataAccessToken } = res;
// re-authenticate with the new id tokens to set new node auth tokens
await this.authenticate({
idTokens,
accessToken,
metadataAccessToken,
authConnection: this.state.authConnection,
authConnectionId: this.state.authConnectionId,
groupedAuthConnectionId: this.state.groupedAuthConnectionId,
userId: this.state.userId,
skipLock: true,
});

Fix in CursorFix in Web


Bug: Vault Access Broken by Token Change

The SeedlessOnboardingController's vault validation and parsing (#assertIsValidVaultData, #parseVaultData) were updated to require accessToken and no longer accept authTokens (or nodeAuthTokens). This breaks backward compatibility, preventing existing users from unlocking their vaults and causing loss of account access, as no migration path is provided.

packages/seedless-onboarding-controller/src/SeedlessOnboardingController.ts#L1670-L1689

*/
#assertIsValidVaultData(value: unknown): asserts value is VaultData {
// value is not valid vault data if any of the following conditions are true:
if (
!value || // value is not defined
typeof value !== 'object' || // value is not an object
!('toprfEncryptionKey' in value) || // toprfEncryptionKey is not defined
typeof value.toprfEncryptionKey !== 'string' || // toprfEncryptionKey is not a string
!('toprfPwEncryptionKey' in value) || // toprfPwEncryptionKey is not defined
typeof value.toprfPwEncryptionKey !== 'string' || // toprfPwEncryptionKey is not a string
!('toprfAuthKeyPair' in value) || // toprfAuthKeyPair is not defined
typeof value.toprfAuthKeyPair !== 'string' || // toprfAuthKeyPair is not a string
!('revokeToken' in value) || // revokeToken is not defined
typeof value.revokeToken !== 'string' || // revokeToken is not a string
!('accessToken' in value) || // accessToken is not defined
typeof value.accessToken !== 'string' // accessToken is not a string
) {
throw new Error(SeedlessOnboardingControllerErrorMessage.VaultDataError);
}
}

packages/seedless-onboarding-controller/src/SeedlessOnboardingController.ts#L1521-L1565

*/
#parseVaultData(data: unknown): {
toprfEncryptionKey: Uint8Array;
toprfPwEncryptionKey: Uint8Array;
toprfAuthKeyPair: KeyPair;
revokeToken: string;
accessToken: string;
} {
if (typeof data !== 'string') {
throw new Error(
SeedlessOnboardingControllerErrorMessage.InvalidVaultData,
);
}
let parsedVaultData: unknown;
try {
parsedVaultData = JSON.parse(data);
} catch {
throw new Error(
SeedlessOnboardingControllerErrorMessage.InvalidVaultData,
);
}
this.#assertIsValidVaultData(parsedVaultData);
const rawToprfEncryptionKey = base64ToBytes(
parsedVaultData.toprfEncryptionKey,
);
const rawToprfPwEncryptionKey = base64ToBytes(
parsedVaultData.toprfPwEncryptionKey,
);
const parsedToprfAuthKeyPair = JSON.parse(parsedVaultData.toprfAuthKeyPair);
const rawToprfAuthKeyPair = {
sk: BigInt(parsedToprfAuthKeyPair.sk),
pk: base64ToBytes(parsedToprfAuthKeyPair.pk),
};
return {
toprfEncryptionKey: rawToprfEncryptionKey,
toprfPwEncryptionKey: rawToprfPwEncryptionKey,
toprfAuthKeyPair: rawToprfAuthKeyPair,
revokeToken: parsedVaultData.revokeToken,
accessToken: parsedVaultData.accessToken,
};
}

Fix in CursorFix in Web


Bug: Token Assignment Inconsistency

The authenticate method's accessToken and metadataAccessToken parameters are typed as required strings, but their assignment to the controller state is conditional. This inconsistency can lead to falsy string values being ignored and is a breaking change for existing callers who now must provide these parameters.

packages/seedless-onboarding-controller/src/SeedlessOnboardingController.ts#L313-L320

}
if (accessToken) {
state.accessToken = accessToken;
}
if (metadataAccessToken) {
state.metadataAccessToken = metadataAccessToken;
}
});

packages/seedless-onboarding-controller/src/SeedlessOnboardingController.ts#L264-L277

*/
async authenticate(params: {
idTokens: string[];
accessToken: string;
metadataAccessToken: string;
authConnection: AuthConnection;
authConnectionId: string;
userId: string;
groupedAuthConnectionId?: string;
socialLoginEmail?: string;
refreshToken?: string;
revokeToken?: string;
skipLock?: boolean;
}) {

Fix in CursorFix in Web


Bug: Inconsistent Auth Validation Causes Failures

The authenticate method now requires accessToken and metadataAccessToken parameters, a breaking change for existing callers. Additionally, metadataAccessToken is strictly validated as required for all authenticated users, which may cause authentication failures for existing users. This introduces inconsistent validation, as accessToken is required for vault creation but not consistently asserted for authenticated users.

packages/seedless-onboarding-controller/src/SeedlessOnboardingController.ts#L1608-L1627

) {
throw new Error(
SeedlessOnboardingControllerErrorMessage.InsufficientAuthToken,
);
}
if (!('refreshToken' in value) || typeof value.refreshToken !== 'string') {
throw new Error(
SeedlessOnboardingControllerErrorMessage.InvalidRefreshToken,
);
}
if (
!('metadataAccessToken' in value) ||
typeof value.metadataAccessToken !== 'string'
) {
throw new Error(
SeedlessOnboardingControllerErrorMessage.InvalidMetadataAccessToken,
);
}
}

packages/seedless-onboarding-controller/src/SeedlessOnboardingController.ts#L264-L277

*/
async authenticate(params: {
idTokens: string[];
accessToken: string;
metadataAccessToken: string;
authConnection: AuthConnection;
authConnectionId: string;
userId: string;
groupedAuthConnectionId?: string;
socialLoginEmail?: string;
refreshToken?: string;
revokeToken?: string;
skipLock?: boolean;
}) {

Fix in CursorFix in Web


Was this report helpful? Give feedback by reacting with 👍 or 👎

@himanshuchawla009 himanshuchawla009 merged commit d3e096e into main Jul 15, 2025
211 checks passed
@himanshuchawla009 himanshuchawla009 deleted the feat/persist-access-token branch July 15, 2025 06:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants