Skip to content

Cursor Encoding

DodaTech 2 min read

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

  1. Why encode cursors at all?
  2. What is the difference between base64 and signed encoding?
  3. How do you handle expired cursors?
  4. What happens if a cursor is tampered with?
  5. How do you include sort values in cursor encoding?

Answers:

  1. To hide internal details and prevent manipulation.
  2. Base64 is opaque; HMAC signing also prevents tampering.
  3. Include a timestamp in the cursor and reject expired ones.
  4. HMAC check fails; return a 400 error.
  5. 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

Should I use base64 or HMAC cursors?

: Base64 for simple opacity. HMAC for security against tampering.

Can cursors be shared across users?

: Yes. Cursors don't contain auth information.

How long should a cursor remain valid?

: Typically 30-60 minutes. Include expiration in the cursor payload.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro