By default, Graphinate uses a fast and simple ID serialization mechanism based on Python's repr() and base64. While convenient, this approach relies on ast.literal_eval(), which, although safer than eval(), might not meet the security requirements of high-risk environments (e.g., public-facing APIs where ID tampering is a concern).
This recipe demonstrates how to implement a Signed ID Converter using HMAC-SHA256 to ensure that IDs cannot be tampered with.
importastimportbase64importhashlibimporthmacimportosfromtypingimportAnyimportgraphinate.converters# 1. Define your secret key (load from env in production)SECRET_KEY=os.environ.get("MY_APP_SECRET","change-me-in-prod").encode()defsign(payload:bytes)->bytes:"""Generate HMAC-SHA256 signature."""returnhmac.new(SECRET_KEY,payload,hashlib.sha256).digest()defsecure_encode(value:Any,encoding:str='utf-8')->str:"""Encodes an object into a signed, Base64 string."""# 1. Serialize payloadobj_s=repr(value)obj_b=obj_s.encode(encoding)# 2. Sign payloadsignature=sign(obj_b)# 3. Pack (Signature + Payload)packet=signature+obj_b# 4. Base64 Encodereturnbase64.urlsafe_b64encode(packet).decode(encoding)defsecure_decode(value:str,encoding:str='utf-8')->Any:"""Decodes and verifies a signed ID."""try:packet=base64.urlsafe_b64decode(value.encode(encoding))exceptException:raiseValueError("Invalid Base64")iflen(packet)<32:raiseValueError("Token too short")# 1. Unpacksignature=packet[:32]payload_b=packet[32:]# 2. Verify Signatureexpected_signature=sign(payload_b)ifnothmac.compare_digest(signature,expected_signature):raiseValueError("Invalid Signature - ID tampered with!")# 3. Deserializeobj_s=payload_b.decode(encoding)returnast.literal_eval(obj_s)# 4. Apply the patch# Note: In a real application, you might want to subclass the Builder # or inject these functions rather than monkey-patching.graphinate.converters.encode=secure_encodegraphinate.converters.decode=secure_decode# Now all IDs generated by Graphinate will be signed!