Authentication
Secure your API with JWT authentication and PostgreSQL Row-Level Security.
JWT Authentication
Postrust validates JWT tokens and extracts role claims to determine database access.
{
"role": "authenticated_user",
"sub": "user_123",
"email": "[email protected]",
"exp": 1704067200
}curl http://localhost:3000/users \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."How the claims are checked
A claim that is absent is not a claim that is wrong: a token carrying no exp does not expire, and one carrying no aud is for anybody. Only a claim that is present and is not the kind of thing that claim is gets rejected.
- expChecked to the second. Forgiving an expiry keeps a session alive past the moment its issuer said it ended, so no slack is given here.
- nbf, iatThirty seconds of clock skew allowed. These describe a token that is not valid yet, and a client whose clock runs slightly fast mints one through nobody's fault; refusing it makes a working deployment fail intermittently.
- roleMust be a string. A role claim of any other shape names no role and falls back to the anonymous role, rather than asking PostgreSQL for a role of that spelling.
A 401 names which refusal in its WWW-Authenticate challenge, so a client can tell “rotate your key” from “fix your clock” without reading the body.
Configuration
PGRST_JWT_SECRETSecret key for HS256/384/512 validation
PGRST_JWT_SECRET_IS_BASE64Set true if secret is base64 encoded
PGRST_JWT_AUDRequired audience claim (optional)
PGRST_JWT_ROLE_CLAIM_KEYClaim key containing role (default: role)
Row-Level Security
PostgreSQL RLS policies are enforced on every request. JWT claims are available as session variables.
-- Enable RLS on table
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
-- Policy: users can only see their own orders
CREATE POLICY user_orders ON orders
FOR ALL
USING (
user_id = current_setting('request.jwt.claims')::json->>'sub'
);
-- Policy: admins can see all orders
CREATE POLICY admin_orders ON orders
FOR ALL
USING (
current_setting('request.jwt.claims')::json->>'role' = 'admin'
);Accessing JWT Claims in SQL
-- Get full claims object
current_setting('request.jwt.claims')::json
-- Get specific claim
current_setting('request.jwt.claims')::json->>'sub'
current_setting('request.jwt.claims')::json->>'email'
current_setting('request.jwt.claims')::json->>'role'
-- Use in function
CREATE FUNCTION get_current_user_id() RETURNS TEXT AS $$
SELECT current_setting('request.jwt.claims')::json->>'sub';
$$ LANGUAGE SQL STABLE;The GraphQL surface
Everything above is how the REST surface authenticates. The GraphQL surface at /v1/graphql reads Hasura’s contract instead — an admin secret, x-hasura-* session variables, and a role that decides which schema answers before it decides anything about a row.
The two meet at the token: a verified JWT names the role either way, and on the GraphQL side an X-Hasura-Role header may select any role the token’s x-hasura-allowed-roles lists. See Configuration for the settings and GraphQL for the surface itself.