{"record":{"id":"c058f655f7990376","repo":"odoo/odoo","slug":"id-and-raw-id-were-not-equivalent","errorCode":null,"errorMessage":"id and raw_id were not equivalent","messagePattern":"id and raw_id were not equivalent","errorType":"exception","errorClass":"InvalidAuthenticationResponse","httpStatus":null,"severity":"error","filePath":"addons/auth_passkey/_vendor/webauthn/authentication/verify_authentication_response.py","lineNumber":85,"sourceCode":"        - `credential_public_key`: The public key for the credential's ID as provided in a\n          preceding authenticator registration ceremony.\n        - `credential_current_sign_count`: The current known number of times the authenticator was\n          used.\n        - (optional) `require_user_verification`: Whether or not to require that the authenticator\n          verified the user.\n\n    Returns:\n        Information about the authenticator\n\n    Raises:\n        `helpers.exceptions.InvalidAuthenticationResponse` if the response cannot be verified\n    \"\"\"\n    if isinstance(credential, str) or isinstance(credential, dict):\n        credential = parse_authentication_credential_json(credential)\n\n    # FIDO-specific check\n    if bytes_to_base64url(credential.raw_id) != credential.id:\n        raise InvalidAuthenticationResponse(\"id and raw_id were not equivalent\")\n\n    # FIDO-specific check\n    if credential.type != PublicKeyCredentialType.PUBLIC_KEY:\n        raise InvalidAuthenticationResponse(\n            f'Unexpected credential type \"{credential.type}\", expected \"public-key\"'\n        )\n\n    response = credential.response\n\n    client_data_bytes = byteslike_to_bytes(response.client_data_json)\n    authenticator_data_bytes = byteslike_to_bytes(response.authenticator_data)\n    signature_bytes = byteslike_to_bytes(response.signature)\n\n    client_data = parse_client_data_json(client_data_bytes)\n\n    if client_data.type != ClientDataType.WEBAUTHN_GET:\n        raise InvalidAuthenticationResponse(\n            f'Unexpected client data type \"{client_data.type}\", expected \"{ClientDataType.WEBAUTHN_GET}\"'","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/odoo/odoo/blob/1e661df964b1b264c9cef3ab28430d4785be3fda/addons/auth_passkey/_vendor/webauthn/authentication/verify_authentication_response.py#L67-L103","documentation":"WebAuthn authentication verification rejects a credential whose 'id' string is not the base64url encoding of its 'raw_id' bytes. The spec requires both to represent the same credential ID; a mismatch means the JSON was hand-built or mangled in transit.","triggerScenarios":"Calling verify_authentication_response() with a credential dict/JSON where 'id' was re-encoded (e.g. base64 instead of base64url, padding added/stripped) or where raw_id bytes were decoded with the wrong charset. Happens when a frontend passes navigator.credentials.get() output through a JSON layer that re-encodes ArrayBuffers.","commonSituations":"Custom JS serializing the PublicKeyCredential manually and encoding rawId with btoa (base64 with +/ and padding) instead of base64url; a middleware or ORM that base64-decodes then re-encodes fields; passing a dict built from database-stored fields with inconsistent encodings.","solutions":["On the client, send PublicKeyCredentialJSON via credentials.toJSON() (native) or base64url-encode rawId so id === base64url(rawId)","If building the dict server-side, derive id from raw_id: credential['id'] = base64url_to_bytes-style encoding of raw_id","Log both values and confirm which side (client serialization vs server storage) re-encoded the ID"],"exampleFix":"// before: manual serialization\nconst body = { id: btoa(String.fromCharCode(...new Uint8Array(cred.rawId))), rawId: Array.from(new Uint8Array(cred.rawId)), ... };\n// after: native toJSON\nconst body = cred.toJSON(); // id and rawId are consistent base64url","handlingStrategy":"validation","validationCode":"import base64\n\ndef credential_ids_consistent(cred: dict) -> bool:\n    try:\n        raw = base64.urlsafe_b64decode(cred['rawId'] + '==')\n        return base64.urlsafe_b64encode(raw).rstrip(b'=').decode() == cred['id']\n    except Exception:\n        return False","typeGuard":"def is_wellformed_assertion_json(cred) -> bool:\n    return (\n        isinstance(cred, dict)\n        and isinstance(cred.get('id'), str)\n        and isinstance(cred.get('rawId'), str)\n        and isinstance(cred.get('response'), dict)\n        and all(isinstance(cred['response'].get(k), str)\n                for k in ('clientDataJSON', 'authenticatorData', 'signature'))\n    )","tryCatchPattern":"from auth_passkey._vendor.webauthn.helpers.exceptions import InvalidAuthenticationResponse\ntry:\n    result = verify_authentication_response(...)\nexcept InvalidAuthenticationResponse as e:\n    logger.warning('assertion rejected: %s', e)\n    return HTTPResponse(400, 'Passkey verification failed')  # never leak internals to client","preventionTips":["Always serialize with the browser-native cred.toJSON(); do not hand-encode rawId","Add a payload schema check (keys + base64url decodability) at the API boundary before verification","Keep one shared encode/decode utility for base64url on both client and server"],"tags":["webauthn","passkey","authentication","serialization"],"backgroundTag":null,"analyzedSha":"1e661df964b1b264c9cef3ab28430d4785be3fda","analyzedAt":"2026-08-15T05:22:16.142Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}