Kinde + Supabase

Hi, I'm trying to enable RLS in Supabase by syncing the Kinde Client Secret with the Supabase JWT Secret. The idea is to parse the 'sub' attribute from the JWT and then match it against a user_id value in the target db table. (before granting SELECT permission) I've been following this handy guide: https://kinde.com/blog/engineering/kinde-with-supabase/ But I'm using the Next.js app router, not the pages router, and I need some help understanding how to adapt this to the app router. I can get the frontend to successfully retrieve data with RLS disabled, but I can't seem to get RLS to work. I suspect that the token may not be passing successfully to Supabase??.. even though according to the guide, "Supabase automatically authenticates the API connections with the JWT token that we have setup. Since our Kinde and Supabase token secrets are the same (See the Supabase Setup above), the authentication handshake should be done automatically and we do not have to do anything else." I'm also unclear about how to implement the Supabase db function (get_user_id() in the guide) - does this get referenced in the RLS policy itself? Or does it automatically trigger based on a db SELECT call? Thanks very much for any advice !! I'm somewhat of a newbie to all this!
Kinde Blog
Kinde with Supabase
Kinde handles user authentication, manages session tokens, and offers user management features, which can be integrated with the authorization mechanisms provided by Supabase, utilizing PostgreSQL's Row Level Security (RLS) policies.
4 Replies
Peteswah
Peteswah7mo ago
Hey @truffle_search , in terms of setup with App Router, these docs may be able to help https://docs.kinde.com/developer-tools/sdks/backend/nextjs-sdk/ I'm not 100% sure about the supabase stuff, but it sounds like the function defined in the SQL Editor should be the same one referenced in the RLS: https://github.com/orgs/supabase/discussions/1849#discussioncomment-837636
Kinde docs
NextJS App Router SDK
Our developer tools provide everything you need to get started with Kinde.
GitHub
Integrating a custom auth flow with Supabase client while taking ad...
In this discussion, @kiwicopple said: If you wanted to still use Row Level Security, then you would need to add the user's JWT to the client library: const headers = { Authorization: 'Beare...
Peteswah
Peteswah7mo ago
There could be a typo in the blog post on our side
truffle_search
truffle_searchOP7mo ago
@Peter (Kinde) thanks very much for your response! I was able to set up some logging, and it seems that the token that is being passed to Supabase looks like this: {"exp":2031043094,"iat":1715467094,"iss":"supabase","ref":"chxdqfnsegmawxamcxmn","role":"anon"} According to the guide, by syncing the Kinde Client Secret with the Supabase JWT Secret, I should be able to get the Kinde user_id as the 'sub' attribute in the token. I'll look into passing a token through the header as a workaround, but I'm sure I'm just missing something obvious. Again, thank you! I finally figured this out and thought I'd report back: Step 1: get the kinde token
const kindeSession = getKindeServerSession();
const kindeToken = await kindeSession.getIdToken();
const kindeSession = getKindeServerSession();
const kindeToken = await kindeSession.getIdToken();
Step 2: sign the token using the JWT secret (set your JWT secret in Supabase to be the same as your Kinde Client Secret). I used the 'jsonwentoken' package. Super simple.
var jwt = require('jsonwebtoken');
const secret = process.env.KINDE_CLIENT_SECRET;
var accessToken = jwt.sign(kindeToken, secret);
var jwt = require('jsonwebtoken');
const secret = process.env.KINDE_CLIENT_SECRET;
var accessToken = jwt.sign(kindeToken, secret);
Step 3: Pass the signed token along with your Supabase url and key into a 'createClient' object:
import { createClient, SupabaseClient, SupabaseClientOptions } from '@supabase/supabase-js';

interface CustomSupabaseOptions extends SupabaseClientOptions<any> {
global?: {
headers: {
Authorization: string;
};
};
}

export const getSupabase = (access_token: string): SupabaseClient<any> => {
const options: CustomSupabaseOptions = {
global: {
headers: {
Authorization: `Bearer ${access_token}`
}
}
};

return createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
options
);
};
import { createClient, SupabaseClient, SupabaseClientOptions } from '@supabase/supabase-js';

interface CustomSupabaseOptions extends SupabaseClientOptions<any> {
global?: {
headers: {
Authorization: string;
};
};
}

export const getSupabase = (access_token: string): SupabaseClient<any> => {
const options: CustomSupabaseOptions = {
global: {
headers: {
Authorization: `Bearer ${access_token}`
}
}
};

return createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
options
);
};
Step 4: Now you can make your db call with something like:
const { data, error } = await getSupabase (token)
.from('your_table_name')
.select('*');
const { data, error } = await getSupabase (token)
.from('your_table_name')
.select('*');
Step 5: You are finally in a position to enable RLS by setting up a relation like this: (get_user_id() = user_id) where get_user_id() parses out the sub attribute of your token that contains the user name.
Peteswah
Peteswah6mo ago
Thanks so much @truffle_search !
Want results from more Discord servers?
Add your server