vrana/adminer · error · Error

The passkey does not support storing passwords.

Error message

The passkey does not support storing passwords.

What it means

This error is thrown by the login-passkey plugin's passkeyGet() when, after a successful WebAuthn navigator.credentials.get() assertion, the credential's client extension results contain no usable 'prf' extension output (no prf object or no prf.results). The plugin relies on the WebAuthn PRF (pseudo-random function) extension to derive an encryption key and server passwords from the passkey, so an authenticator/browser that does not evaluate PRF cannot be used for password storage. The message text comes from passkeyLang.unsupported, which reads 'The passkey does not support storing passwords.'

Solutions

  1. Use a different passkey/authenticator that supports the WebAuthn prf extension (e.g. a modern security key such as YubiKey firmware 5.2+, or a current browser platform authenticator).
  2. Update the browser to a version supporting the prf extension in navigator.credentials (Chrome 117+/Edge 117+, Firefox 119+, Safari 18+).
  3. Set up the passkey in an environment known to evaluate PRF, then retry the login/decryption flow with that passkey.
  4. If PRF cannot be enabled, stop using the passkey-based password storage feature and store server passwords outside the passkey-encrypted store.

Example fix

null
Defensive patterns

Strategy: try-catch

Validate before calling

// Before invoking the passkey login/decryption flow:
function prfSupportedInGet() {
  return typeof PublicKeyCredential !== 'undefined' &&
    typeof navigator.credentials !== 'undefined';
}
// Note: authenticator PRF support cannot be fully known before an assertion;
// guard the extension result itself:
function prfResultsAvailable(credential) {
  const prf = credential.getClientExtensionResults().prf;
  return !!(prf && prf.results && prf.results.first);
}

Type guard

function hasPrfResults(credential) {
  const prf = credential.getClientExtensionResults().prf;
  return typeof prf === 'object' && prf !== null &&
    typeof prf.results === 'object' && prf.results !== null &&
    prf.results.first instanceof ArrayBuffer;
}

Try / catch

try {
  const { key, id, password } = await passkeyGet(id);
  // proceed with decrypted accounts
} catch (err) {
  if (err.message === 'The passkey does not support storing passwords.') {
    showFallbackPasswordPrompt(); // ask user for a PRF-capable passkey
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Calling passkeyGet() (via loginFormField's stored-password flow) with a passkey whose authenticator does not support or did not evaluate the prf extension: credential.getClientExtensionResults().prf is undefined, or prf.results is missing, even though the assertion itself succeeded.

Common situations: Authenticators without PRF support (many older security keys, Windows Hello on some versions, some phone passkeys), browsers that do not implement the prf extension in credentials.get(), passkeys synced through providers that strip extensions (e.g. some iCloud/Google synced passkeys), or a UV-free/unsupported flow where userVerification was not enforced by the device.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.


AI-assisted analysis of vrana/adminer@5106d54c84 (2026-09-13). Data as JSON: /api/errors/8430d5909500632c. Report an issue: GitHub.

Appendix: source

Thrown at plugins/login-passkey.php:168

		crypto.subtle.importKey('raw', prf, {name: 'AES-GCM'}, false, ['encrypt', 'decrypt']),
		crypto.subtle.digest('SHA-256', password).then(passkeyHex),
	]).then(derived => ({key: derived[0], id: passkeyUrl(rawId), password: derived[1]}));
}

/** Get the encryption key from a passkey
* @param {string} id base64url encoded ID of the passkey, empty string to use any
* @return {Promise} resolves to {key: CryptoKey, id: string, password: string}
*/
function passkeyGet(id) {
	return navigator.credentials.get({publicKey: {
		challenge: passkeyRandom(32), // the assertion is not verified, the key is verified by decrypting the accounts
		allowCredentials: (id ? [{type: 'public-key', id: passkeyBytes(id)}] : []),
		userVerification: 'required', // the PRF extension requires it
		extensions: {prf: {eval: {first: passkeySalt}}},
	}}).then(credential => {
		const prf = credential.getClientExtensionResults().prf;
		if (!prf || !prf.results) {
			throw new Error(passkeyLang.unsupported);
		}
		return passkeyDerive(prf.results.first, credential.rawId);
	});
}

/** Create a new passkey
* @return {Promise} resolves to {key: CryptoKey, id: string, password: string}
*/
function passkeyCreate() {
	return navigator.credentials.create({publicKey: {
		challenge: passkeyRandom(32),
		rp: {name: 'Adminer'},
		user: {id: passkeyRandom(16), name: 'Adminer', displayName: 'Adminer'},
		pubKeyCredParams: [{type: 'public-key', alg: -7}, {type: 'public-key', alg: -257}],
		authenticatorSelection: {residentKey: 'preferred', userVerification: 'required'},
		extensions: {prf: {eval: {first: passkeySalt}}},
	}}).then(credential => {
		const prf = credential.getClientExtensionResults().prf;

View on GitHub (pinned to 5106d54c84)