Skip to main content

Auth & Context

ONQL has no opinion about how you authenticate users — that's your gateway's job. What ONQL provides is what to do once you know who's calling: turn an identity into a ctxkey + ctxvalues pair, and ONQL takes it from there.

The two-layer model​

Client  ──HTTP──▶  Your gateway  ──TCP──▶  ONQL
(auth happens here) (context applies here)

Your gateway is in charge of:

  1. Verifying the JWT / session / API key.
  2. Deciding what role the caller has (account, admin, support, ...).
  3. Forwarding the query to ONQL with the right ctxkey and ctxvalues.

ONQL is in charge of:

  1. Looking up the matching context rule on every entity touched by the query.
  2. Splicing the rule's filter into the query plan.
  3. Running the resulting (now-scoped) query.

A typical gateway​

// Express example — pseudocode
app.post('/onql', authenticate, async (req, res) => {
const { query } = req.body;
const result = await onqlClient.query({
protopass: process.env.ONQL_PASS,
query,
ctxkey: req.user.role, // "account" | "admin" | ...
ctxvalues: [req.user.id], // substituted for $1
});
res.json(result);
});

That's the entire backend. There are no entity-specific endpoints, no per-resource permission checks.

What if you skip the gateway?​

If you let raw clients talk to ONQL directly, anyone who picks any ctxkey gets that level of access. The protocol can't tell who you are — only your gateway can. So:

  • For browsers: use a thin gateway. It's 30 lines of code.
  • For server-to-server: a gateway is still recommended. Treat ONQL like you'd treat a database — never expose the raw port to the public internet.

Default-deny​

A safe gateway pattern is default-deny:

const ALLOWED_KEYS = ['account', 'admin', 'support'];
if (!ALLOWED_KEYS.includes(req.user.role)) {
return res.status(403).end();
}

Combined with context rules in the protocol, you get a clean two-layer guarantee:

  1. The gateway decides which role applies.
  2. The protocol decides which rows that role can see.