Skip to main content
Version: Draft

PostGraphile JWT guide

JWTs are optional in PostGraphile. You can authenticate users with session cookies, API keys, mTLS, or any other mechanism that lets your web framework identify who is making the request. The key requirement is that you surface the relevant details to PostgreSQL via pgSettings so that Row Level Security (RLS) policies and functions can make decisions. See the security page for the broader architecture and trade-offs.

Assuming that you choose to use JWTs to provide authentication to PostGraphile (and the author would encourage you to consider alternatives), this guide explains how JWTs fit into that picture, how to populate pgSettings from a verified token, and when you might lean on the postgraphile/presets/lazy-jwt convenience preset.

You own JWT verification

You are expected to verify tokens in your server framework (Express, Koa, Fastify, etc.) and feed the resulting claims to PostGraphile via the preset.grafast.context callback's pgSettings property. This means you can implement refresh tokens, revocation, clock skew handling, and other behaviours that PostGraphile cannot reasonably guess for you.

Typical request flow

  1. Your web framework receives the request and runs whichever authentication middleware you have chosen (sessions, JWTs, mutual TLS, etc.).
  2. After verification you extract the claims you want PostgreSQL to know about and pass them into pgSettings via preset.grafast.context.
  3. PostgreSQL reads those values with current_setting(...) inside policies and functions, completing the authorization story.

For JWT-based setups the first step is often done with jsonwebtoken, jose, or vendor SDKs. The second step is where PostGraphile comes in.

Populating pgSettings after verifying a JWT

Below is an Express-flavoured example that validates a Bearer token and exposes select claims to PostgreSQL.

graphile.config.ts
import { PostGraphileAmberPreset } from "postgraphile/presets/amber";
import { verify } from "jsonwebtoken";

const preset: GraphileConfig.Preset = {
extends: [PostGraphileAmberPreset],

grafast: {
async context(requestContext, args) {
const req = requestContext.expressv4?.req;
const header = req?.get("authorization") ?? "";
const [, token] = header.split(" ");

const pgSettings = {
...args.contextValue?.pgSettings,
} as Record<string, string>;

if (token) {
try {
const claims = verify(token, process.env.JWT_SECRET!, {
audience: "postgraphile",
algorithms: ["HS256"],
});
if (typeof claims === "object" && claims !== null) {
if (claims.role && typeof claims.role === "string") {
pgSettings.role = claims.role;
}
for (const [key, value] of Object.entries(claims)) {
if (typeof value === "undefined" || value === null) continue;
if (!/^[a-z_][a-z0-9_]*$/i.test(key) || key.length > 52) continue;
pgSettings[`jwt.claims.${key}`] = String(value);
}
}
} catch (e) {
// Let the framework decide how to surface auth errors.
requestContext.expressv4?.res?.status(401);
throw e;
}
}

return {
...args.contextValue,
pgSettings,
};
},
},
};

export default preset;

Key points:

  • You decide which claims to trust and forward. You might strip or rename values, or merge in data from a session store.
  • Anything placed inside pgSettings becomes available via current_setting('...', true) when RLS policies run.
  • Do not leak entire JWT payloads blindly—only forward what your database schema needs.

If you want to understand how claims map onto PostgreSQL session variables, see the JWT specification for the precise rules.

Supplying JWTs from clients

Clients typically send JWTs via the Authorization header using the Bearer scheme:

Authorization: Bearer JWT_TOKEN_HERE

Below are a few JavaScript client examples; adapt them to your own stack.

Apollo Client (v3, HTTP)

const httpLink = createHttpLink({
uri: "/graphql",
});

const authLink = setContext((_, { headers }) => {
const token = getJWTToken();
return {
headers: {
...headers,
...(token ? { authorization: `Bearer ${token}` } : null),
},
};
});

const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
});

If you are using Apollo Client 3 with graphql-ws for subscriptions:

import { GraphQLWsLink } from "@apollo/client/link/subscriptions";
import { createClient } from "graphql-ws";

const token = getJWTToken();

const wsLink = new GraphQLWsLink(
createClient({
url: "ws://localhost:3000/graphql",
connectionParams: token
? { authorization: `Bearer ${token}` }
: {},
}),
);

See the Apollo authentication docs and the subscriptions guide for additional configuration options.

Relay (Modern)

function fetchQuery(operation, variables) {
const token = getJWTToken();
return fetch("/graphql", {
method: "POST",
headers: {
"content-type": "application/json",
authorization: token ? `Bearer ${token}` : "",
},
body: JSON.stringify({
query: operation.text,
variables,
}),
}).then((response) => response.json());
}

const environment = new Environment({
network: Network.create(fetchQuery),
store: new Store(new RecordSource()),
});

Relay’s network layer setup is documented in the Relay guides.

About the lazy-jwt preset

postgraphile/presets/lazy-jwt exists for teams that need a quick stopgap while building a real authentication layer. When enabled it:

  • runs only when you use a grafserv HTTP adaptor;
  • reads the Authorization header for Bearer tokens from requestContext.node?.req?.headers?.authorization (thus only works with adaptors that populate this, such as postgraphile/grafserv/express/v4);
  • verifies the token with jsonwebtoken using a shared secret (set via preset.grafserv.pgJwtSecret or preset.schema.pgJwtSecret), default algorithms HS256/HS384, and default audience postgraphile (override via preset.grafserv.pgJwtVerifyOptions);
  • copies the role claim and alphanumeric claims into pgSettings under the jwt.claims.* namespace.

It does not handle refresh tokens, key rotation, token revocation, custom claim mapping, or multi-tenant secret lookup. Treat it as a temporary measure and plan to replace it with middleware that explicitly sets pgSettings.

Issuing JWTs from PostgreSQL

If you want to generate JWTs from your PostgreSQL schema, PostGraphile can sign composite types listed in preset.gather.pgJwtTypes. When a function returns one of those types the response will instead include a signed JWT string (signed using the preset.schema.pgJwtSecret and any preset.schema.pgJwtSignOptions). Combine that with your own authentication SQL to issue tokens. Remember that this feature does not manage refresh tokens or provide a complete auth system— it only signs the composite payloads you return.

Choosing between JWTs and alternatives

JWTs are one option amongst many. Session cookies, OAuth-backed passport strategies, or bespoke API keys all work as long as you populate pgSettings appropriately. The pgSettings section of the config docs shows how to expose request data regardless of authentication style, and the security page lists common approaches. Pick the strategy that fits your infrastructure, and make sure you're aware of the trade-offs of each approach.