Using Custom JWTs with Neon Authorize
A Step-by-step guide for using custom JWTs with Neon Authorize
Demo project
Related docs
Neon Authorize lets you implement authorization directly within your Postgres database using Row-Level Security (RLS). While it works seamlessly 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 Authorize, covering:
- Generating keys for JWT signing
- Creating custom JWTs with specific claims
- Configuring Neon 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 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 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 Authorize
Now, let's configure Neon Authorize to trust your custom JWTs:
Expose Your Public Key
Neon 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 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 Authorize
- Go to the Authorize page in your Neon console.
- 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 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 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 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 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.