Cursor Encoding
title: "Cursor Encoding — Securely Encoding and Decoding Pagination Cursors" description: "Cursor encoding transforms database identifiers into opaque, tamper-resistant strings for cursor-based pagination, using base64 or signed tokens for security." date: 2026-06-28 lastmod: 2026-06-28 weight: 23 tags: [apis, pagination] }
Cursor encoding converts internal database pointers into opaque, tamper-resistant strings using base64 encoding with optional signing to prevent manipulation.
What You'll Learn
- Base64 encoding for cursor values
- HMAC signing for tamper prevention
- Decoding strategies on the server
Why It Matters
Raw cursor values (database IDs) expose internal implementation details. Encoded cursors prevent clients from guessing or manipulating cursor values.
Code Examples
import base64
import json
import hmac
import hashlib
# Simple base64 encoding
def encode_cursor(value):
"""Encode a value as an opaque cursor string."""
payload = json.dumps({"v": value, "t": int(time.time())})
return base64.urlsafe_b64encode(payload.encode()).decode()
def decode_cursor(cursor):
"""Decode a cursor string back to its value."""
try:
payload = base64.urlsafe_b64decode(cursor.encode())
data = json.loads(payload)
return data["v"]
except (ValueError, json.JSONDecodeError, KeyError):
return None
# Signed cursor with HMAC
SECRET_KEY = "your-server-secret"
def encode_signed_cursor(value):
"""Encode a cursor with HMAC signature to prevent tampering."""
payload = json.dumps({"v": value})
encoded = base64.urlsafe_b64encode(payload.encode()).decode()
signature = hmac.new(
SECRET_KEY.encode(), encoded.encode(), hashlib.sha256
).hexdigest()[:16]
return f"{encoded}.{signature}"
def decode_signed_cursor(cursor):
"""Decode and verify a signed cursor."""
try:
encoded, signature = cursor.split(".")
expected = hmac.new(
SECRET_KEY.encode(), encoded.encode(), hashlib.sha256
).hexdigest()[:16]
if not hmac.compare_digest(signature, expected):
return None
payload = base64.urlsafe_b64decode(encoded.encode())
return json.loads(payload)["v"]
except (ValueError, json.JSONDecodeError, KeyError):
return None
Common Mistakes
1. Exposing Raw Database IDs
Clients could enumerate resources by incrementing IDs.
2. Using Unsigned Cursors
Malicious clients could modify cursor values to probe other data.
3. Including Sensitive Data in Cursors
Cursors should only contain pagination pointers, not sensitive info.
4. No Decoding Error Handling
Invalid cursors should return 400, not crash the server.
5. Cursor Too Large
Cursor length increases with payload size. Keep cursors small.
Practice Questions
- Why encode cursors at all?
- What is the difference between base64 and signed encoding?
- How do you handle expired cursors?
- What happens if a cursor is tampered with?
- How do you include sort values in cursor encoding?
Answers:
- To hide internal details and prevent manipulation.
- Base64 is opaque; HMAC signing also prevents tampering.
- Include a timestamp in the cursor and reject expired ones.
- HMAC check fails; return a 400 error.
- Include sort value and ID in the cursor JSON payload.
Challenge: Implement a cursor encoding system that includes a sort value (created_at), a unique ID, and an HMAC signature. Support cursor expiration with timestamps.
FAQ
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro