DocsAuthentication

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.

JWT Payload
{
  "role": "authenticated_user",
  "sub": "user_123",
  "email": "[email protected]",
  "exp": 1704067200
}
Request with JWT
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_SECRET

Secret key for HS256/384/512 validation

PGRST_JWT_SECRET_IS_BASE64

Set true if secret is base64 encoded

PGRST_JWT_AUD

Required audience claim (optional)

PGRST_JWT_ROLE_CLAIM_KEY

Claim key containing role (default: role)

Row-Level Security

PostgreSQL RLS policies are enforced on every request. JWT claims are available as session variables.

SQL - RLS Policy
-- 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.