Skip to content

PostgREST JWT Authentication

Beau Barker edited this page Nov 13, 2025 · 31 revisions

Enabling JWT authentication in PostgREST forces clients to authenticate using a JWT token in the Authorization header:

Authorization: Bearer eyJhbGciOiJIUzI1...

The minimal JWT payload must include at least a role.

{
  "role": "user"
}

Until JWT authentication is enabled (pgrst.jwt_secret set), PostgREST is not secured and anyone can access it.

Create a JWT Secret

Note

PostgREST can share the JWT secret with Caddy.

Generate a secret:

openssl rand -base64 32

PostgREST

Put the secret in the environment file:

app/.env

JWT_SECRET=(your secret)

Add the secret to the PostgREST service:

postgrest:
  environment:
    PGRST_JWT_SECRET: ${JWT_SECRET:?}
    PGRST_JWT_SECRET_IS_BASE64: true

Postgres

The secret is needed in the Postgres service because a migration will configure PostgREST:

db/.env

JWT_SECRET=(your secret)

db/compose.yaml

postgres:
  environment:
    JWT_SECRET: ${JWT_SECRET:?}

Add to the Postgrest initialisation migration:

db/postgres/migrations/00-init_postgrest.sql

\set pgrst_jwt_secret '$JWT_SECRET'

-- Set the JWT secret in the db - despite it being set in the JWT_SECRET
-- env var, this appears to be also required
alter system set pgrst.jwt_secret = :'pgrst_jwt_secret';

▶️ Run the Migrations

cd db
bin/postgres migrate

Caddy

Split your Caddyfile into public and JWT-protected sections. The private routes require a valid access token:

app/caddy/Caddyfile

{$CADDY_SITE_ADDRESS}

# --- Public routes ---

# PostgREST's OpenAPI endpoint (an optional public endpoint)
handle_path /rest/ {
  reverse_proxy http://postgrest:3000
}

# --- JWT protected routes ---

route {

  # Set the Authorization header from the Cookie header
  # Only if it's not already set
  @noHeader not header Authorization *
  request_header Authorization "Bearer {cookie.access_token}" # fallback to cookie

  # Protected PostgREST endpoints
  handle_path /rest/* {
    reverse_proxy http://postgrest:3000
  }

  handle /rpc/* {
    reverse_proxy http://postgrest:3000
  }

  # .. Other authenticated endpoints ..

}

Restart Caddy for the changes to take effect:

docker compose up -d --force-recreate caddy

Next

  • Continue to PostgREST JWT Management to manage the generation and refreshing of tokens within PostgREST itself.
  • Consider enabling Caddy JWT as well, at the API gateway level, to protect other services as well. It can use the same secret, so a single JWT can be used across all services in addition to PostgREST.
Clone this wiki locally