Package Exports
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@pgpm/jwt-claims) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
@pgpm/jwt-claims
JWT claim handling and validation functions.
Overview
@pgpm/jwt-claims provides PostgreSQL functions for extracting and working with JWT (JSON Web Token) claims stored in PostgreSQL session variables. This package enables seamless integration between JWT-based authentication systems and PostgreSQL, allowing database functions to access user context, group memberships, IP addresses, and other JWT payload data.
Features
- User Context Functions: Extract user ID from JWT claims
- Group Membership: Access user's group IDs
- Request Metadata: Get IP address and user agent from requests
- Database Context: Access database ID from JWT claims
- Type-Safe Extraction: Proper error handling for invalid claim values
- Session Variables: Uses PostgreSQL's
current_setting()for claim storage
Installation
If you have pgpm installed:
pgpm install @pgpm/jwt-claims
pgpm deployThis is a quick way to get started. The sections below provide more detailed installation options.
Prerequisites
# Install pgpm CLI
npm install -g pgpm
# Start local Postgres (via Docker) and export env vars
pgpm docker start
eval "$(pgpm env)"Tip: Already running Postgres? Skip the Docker step and just export your
PG*environment variables.
Add to an Existing Package
# 1. Install the package
pgpm install @pgpm/jwt-claims
# 2. Deploy locally
pgpm deploy Add to a New Project
# 1. Create a workspace
pgpm init --workspace
cd my-app
# 2. Create your first module
pgpm init
cd packages/your-module
# 3. Install a package
pgpm install @pgpm/jwt-claims
# 4. Deploy everything
pgpm deploy --createdb --database mydb1Core Functions
jwt_public.current_user_id()
Extracts the user ID from JWT claims.
Returns: uuid - The current user's ID, or NULL if not set
Usage:
SELECT jwt_public.current_user_id();JWT Claim: jwt.claims.user_id
jwt_public.current_group_ids()
Extracts the user's group IDs from JWT claims.
Returns: uuid[] - Array of group IDs, or empty array if not set
Usage:
SELECT jwt_public.current_group_ids();JWT Claim: jwt.claims.group_ids
jwt_public.current_ip_address()
Extracts the client's IP address from JWT claims.
Returns: text - The client's IP address, or NULL if not set
Usage:
SELECT jwt_public.current_ip_address();JWT Claim: jwt.claims.ip_address
jwt_public.current_user_agent()
Extracts the client's user agent from JWT claims.
Returns: text - The client's user agent string, or NULL if not set
Usage:
SELECT jwt_public.current_user_agent();JWT Claim: jwt.claims.user_agent
jwt_private.current_database_id()
Extracts the database ID from JWT claims (private function).
Returns: uuid - The database ID, or NULL if not set
Usage:
SELECT jwt_private.current_database_id();JWT Claim: jwt.claims.database_id
Usage
Setting JWT Claims
JWT claims are set as PostgreSQL session variables, typically by your authentication middleware:
-- Set user ID claim
SELECT set_config('jwt.claims.user_id', 'user-uuid-here', false);
-- Set group IDs claim
SELECT set_config('jwt.claims.group_ids', '{uuid1,uuid2,uuid3}', false);
-- Set IP address claim
SELECT set_config('jwt.claims.ip_address', '192.168.1.1', false);
-- Set user agent claim
SELECT set_config('jwt.claims.user_agent', 'Mozilla/5.0...', false);
-- Set database ID claim
SELECT set_config('jwt.claims.database_id', 'database-uuid-here', false);Using Claims in Row-Level Security
-- Enable RLS on a table
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
-- Users can only see their own posts
CREATE POLICY user_posts ON posts
FOR ALL
TO authenticated
USING (user_id = jwt_public.current_user_id());
-- Users can see posts from their groups
CREATE POLICY group_posts ON posts
FOR SELECT
TO authenticated
USING (group_id = ANY(jwt_public.current_group_ids()));Using Claims in Functions
-- Function that uses current user ID
CREATE FUNCTION create_post(title text, content text)
RETURNS uuid AS $$
DECLARE
new_post_id uuid;
BEGIN
INSERT INTO posts (user_id, title, content)
VALUES (jwt_public.current_user_id(), title, content)
RETURNING id INTO new_post_id;
RETURN new_post_id;
END;
$$ LANGUAGE plpgsql;
-- Function that checks group membership
CREATE FUNCTION user_in_group(group_id uuid)
RETURNS boolean AS $$
BEGIN
RETURN group_id = ANY(jwt_public.current_group_ids());
END;
$$ LANGUAGE plpgsql;Audit Logging with JWT Claims
-- Audit log table
CREATE TABLE audit_log (
id serial PRIMARY KEY,
user_id uuid,
ip_address text,
user_agent text,
action text,
timestamp timestamptz DEFAULT now()
);
-- Trigger function for audit logging
CREATE FUNCTION log_action()
RETURNS trigger AS $$
BEGIN
INSERT INTO audit_log (user_id, ip_address, user_agent, action)
VALUES (
jwt_public.current_user_id(),
jwt_public.current_ip_address(),
jwt_public.current_user_agent(),
TG_OP || ' on ' || TG_TABLE_NAME
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Add trigger to table
CREATE TRIGGER audit_posts
AFTER INSERT OR UPDATE OR DELETE ON posts
FOR EACH ROW
EXECUTE FUNCTION log_action();Multi-Tenancy with Database ID
-- Filter data by database ID
CREATE FUNCTION get_tenant_data()
RETURNS SETOF my_table AS $$
BEGIN
RETURN QUERY
SELECT * FROM my_table
WHERE database_id = jwt_private.current_database_id();
END;
$$ LANGUAGE plpgsql;Integration with Other Packages
With @pgpm/stamps
The stamps package uses JWT claims for automatic user tracking:
-- Stamps automatically uses jwt_public.current_user_id()
-- for created_by and updated_by columnsWith @pgpm/achievements
The achievements package uses JWT claims for user context:
-- Check current user's achievements
SELECT * FROM status_public.steps_required('newbie');
-- Uses jwt_public.current_user_id() internallyWith @pgpm/default-roles
Combine JWT claims with role-based access:
-- Set role based on JWT claim
CREATE FUNCTION set_user_role()
RETURNS void AS $$
DECLARE
user_role text;
BEGIN
user_role := current_setting('jwt.claims.role', true);
IF user_role = 'admin' THEN
SET LOCAL ROLE administrator;
ELSIF user_role = 'user' THEN
SET LOCAL ROLE authenticated;
ELSE
SET LOCAL ROLE anonymous;
END IF;
END;
$$ LANGUAGE plpgsql;Error Handling
All functions include error handling for invalid claim values:
-- If jwt.claims.user_id is not a valid UUID
SELECT jwt_public.current_user_id();
-- Returns NULL and raises NOTICE: 'Invalid UUID value'
-- If jwt.claims.group_ids is not a valid UUID array
SELECT jwt_public.current_group_ids();
-- Returns empty array [] and raises NOTICE: 'Invalid UUID value'Security Considerations
- Trust the Source: Only set JWT claims from trusted authentication middleware
- Validate Claims: Always validate JWT signatures before setting claims
- Session Scope: Claims are session-scoped and don't persist across connections
- No Direct Access: Users cannot directly modify session variables in most configurations
- Use HTTPS: Always transmit JWTs over HTTPS to prevent interception
Dependencies
@pgpm/types: Core PostgreSQL types@pgpm/verify: Verification utilities
Testing
pnpm testRelated Tooling
- pgpm: 🖥️ PostgreSQL Package Manager for modular Postgres development. Works with database workspaces, scaffolding, migrations, seeding, and installing database packages.
- pgsql-test: 📊 Isolated testing environments with per-test transaction rollbacks—ideal for integration tests, complex migrations, and RLS simulation.
- supabase-test: 🧪 Supabase-native test harness preconfigured for the local Supabase stack—per-test rollbacks, JWT/role context helpers, and CI/GitHub Actions ready.
- graphile-test: 🔐 Authentication mocking for Graphile-focused test helpers and emulating row-level security contexts.
- pgsql-parser: 🔄 SQL conversion engine that interprets and converts PostgreSQL syntax.
- libpg-query-node: 🌉 Node.js bindings for
libpg_query, converting SQL into parse trees. - pg-proto-parser: 📦 Protobuf parser for parsing PostgreSQL Protocol Buffers definitions to generate TypeScript interfaces, utility functions, and JSON mappings for enums.
Disclaimer
AS DESCRIBED IN THE LICENSES, THE SOFTWARE IS PROVIDED "AS IS", AT YOUR OWN RISK, AND WITHOUT WARRANTIES OF ANY KIND.
No developer or entity involved in creating this software will be liable for any claims or damages whatsoever associated with your use, inability to use, or your interaction with other users of the code, including any direct, indirect, incidental, special, exemplary, punitive or consequential damages, or loss of profits, cryptocurrencies, tokens, or anything else of value.