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 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
- Your web framework receives the request and runs whichever authentication middleware you have chosen (sessions, JWTs, mutual TLS, etc.).
- After verification you extract the claims you want PostgreSQL to know about
and pass them into
pgSettingsviapreset.grafast.context. - 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.
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
pgSettingsbecomes available viacurrent_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
grafservHTTP adaptor; - reads the
Authorizationheader for Bearer tokens fromrequestContext.node?.req?.headers?.authorization(thus only works with adaptors that populate this, such aspostgraphile/grafserv/express/v4); - verifies the token with
jsonwebtokenusing a shared secret (set viapreset.grafserv.pgJwtSecretorpreset.schema.pgJwtSecret), default algorithmsHS256/HS384, and default audiencepostgraphile(override viapreset.grafserv.pgJwtVerifyOptions); - copies the
roleclaim and alphanumeric claims intopgSettingsunder thejwt.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.