Using Custom JWTs with Neon RLS Authorize
A step-by-step guide for using custom JWTs with Neon RLS Authorize
Demo project
Neon RLS Authorize lets you implement authorization directly within your Postgres database using Row-Level Security (RLS). While it works well with authentication providers like Auth0 and Clerk, you can also use your own custom-generated JSON Web Tokens (JWTs). This gives you fine-grained control over how tokens are created and validated, ideal for specific application needs or when you prefer to manage authentication independently.
This guide walks you through using custom JWTs with Neon RLS Authorize, covering:
- Generating keys for JWT signing
- Creating custom JWTs with specific claims
- Configuring Neon RLS Authorize to validate your JWTs
- Authenticating requests and enforcing RLS policies based on JWT claims
When to use custom JWTs
- Full control: You control your JWTs' structure, claims, and signing process, which is useful when standard providers don't meet your needs.
- Reduced external dependencies: Custom JWTs can reduce reliance on external services, especially for internal tools.
- Tailored security policies: Align JWT generation and validation precisely with your application's unique security requirements
- Simplified architecture in specific cases: For some applications, particularly internal tools or those with existing authentication mechanisms, using custom JWTs can eliminate the need for integrating a separate, full-featured authentication service
Prerequisites
Before starting, make sure you have:
Generate keys for signing JWTs
JWTs are digitally signed to ensure they're authentic and haven't been tampered with. You'll need a public/private key pair for this:
- Private key: Used to sign your JWTs. Keep this secret!
- Public key: Used by Neon RLS Authorize to verify the signatures.
We'll use the jose
library in TypeScript to generate an RSA key pair. Here's a simple way to do it:
Here's what the code does:
- We generate an RSA key pair using the
RS256
algorithm. - We convert the keys to JSON Web Key (JWK) format, a standard way to represent keys.
- We add
kid
(Key ID) andalg
(Algorithm) to the keys for better management. - We save the keys to JSON files.
Security Tips
- Never share your private key. Anyone with it can create valid JWTs.
- Rotate your keys regularly to improve security.
Create your custom JWTs
Now, let's create JWTs in your application. These JWTs will contain "claims" – pieces of information that Neon RLS Authorize will use to enforce your security rules.
Here's an example using jose
, adapted from the demo app:
Let's break it down:
- We load the private key from environment variables (never hardcode it).
- We create a new JWT and add custom claims. Here, we add a
tenant_id
. - We set the algorithm (
alg
), key ID (kid
), subject (sub
- usually the user ID), expiration time (exp
), and issue time (iat
). - We sign the JWT with the private key.
Custom Claims: Add information relevant to your access control. Common claims include:
tenant_id
: For multi-tenant apps.role
: User's role (e.g., "admin", "viewer").permissions
: Specific actions the user can perform.
Set up Neon RLS Authorize
Now, let's configure Neon RLS Authorize to trust your custom JWTs:
Expose Your Public Key
Neon RLS Authorize needs your public key to verify JWT signatures. You'll usually expose it via a JWKS (JSON Web Key Set) endpoint. A JWKS endpoint is a standard way to publish your public keys so that services like Neon RLS Authorize can retrieve them. The demo app uses a Cloudflare Worker for this, serving the key at /.well-known/jwks.json
.
This makes the public key available at https://your-app.com/.well-known/jwks.json
.
Add JWKS url to Neon RLS Authorize
- Go to the Neon Console, navigate to Settings > RLS Authorize.
- Click Add Provider.
- Set the JWKS URL to your endpoint (e.g.,
https://your-app.com/.well-known/jwks.json
). - Click Set Up
- Follow the steps in the UI to set up roles for Neon RLS Authorize. You can ignore the schema-related steps for this guide.
- Note down the connection strings for the
neondb_owner
andauthenticated, passwordless
roles. You'll need both:neondb_owner
: Full privileges, used for migrations.authenticated
: Used by your application, access restricted by RLS.
Authenticate requests and enforce RLS
Finally, let's use the custom JWTs to authenticate requests and enforce RLS policies with drizzle-orm
.
Create RLS Policies
Create RLS policies that use the JWT claims to control access. Use auth.session()
to access custom claims and auth.user_id()
for the sub
claim.
Example from the demo app's schema (src/db/schema.ts
):
Explanation:
tenants
table:read
andmodify
policies useauth.session()->>'tenant_id'
to check thetenant_id
claim, ensuring users only access their tenant's data.users
table:read
andmodify
policies useauth.user_id()
to get the user ID from thesub
claim, restricting users to their own records.
Use custom JWTs
How it works:
- When you send a request with a JWT, Neon RLS Authorize verifies the signature using the public key.
- If the signature is valid and the JWT hasn't expired, the claims are extracted.
- The
pg_session_jwt
extension makes the claims available to RLS policies viaauth.session()
andauth.user_id()
. - RLS policies evaluate the claims to allow or deny access.
Putting it all together
Here's a simplified example combining the key parts:
Conclusion
Using custom JWTs with Neon RLS Authorize offers a powerful way to implement fine-grained authorization in Postgres. By controlling JWT generation and combining it with the security of RLS, you can build robust applications that enforce data access rules at the database level. Remember to secure your private key, design your JWT claims thoughtfully, and test your RLS policies thoroughly.
We hope this guide has helped you understand how to use custom JWTs with Neon RLS Authorize.
Additional Resources
Need help?
Join our Discord Server to ask questions or see what others are doing with Neon. Users on paid plans can open a support ticket from the console. For more details, see Getting Support.