supabase / realtime

Broadcast, Presence, and Postgres Changes via WebSockets
https://supabase.com/realtime
Apache License 2.0
6.81k stars 313 forks source link

Realtime connection failing when custom JWT is used #470

Closed gouthamraj-r closed 1 year ago

gouthamraj-r commented 1 year ago

Bug report

Describe the bug

I'm using supabase self-hosted and auth0 for my authentication instead of the default authentication provided by supabase. So I'm signing my auth0's payload with supabase secret and sending it in headers.

const payload = {
    userId: user.email,
    exp: Math.floor(Date.now() / 1000) + 60 * 60,
}
const accessToken = jwt.sign(payload, SUPABASE_SECRET_KEY)

const options = {}

if (accessToken) {
  options.global = {
    headers: {
      Authorization: `Bearer ${accessToken}`,
    },
  }
}
const supabase = createClient(supabaseUrl, supabaseAnonKey, options)
supabase.channel('custom-update-channel')
.on(
  'postgres_changes',
  { event: 'UPDATE', schema: 'public', table: 'user_notifications' },
  (payload) => { 
      console.log('Change received!', payload)
  }
)
.subscribe()

I also enabled the RLS policy on my table. Using the above headers I'm able to query my database. Now I wanted to enable real-time on my table. But when I try to create a subscription with my custom headers, the Realtime web socket connection throws an Authentication error. When I don't send the custom JWT in the header, it works fine but I need my custom JWT's payload to be stored in the subscription table. realtime so that I can use it on my RLS Policy. What should I do to fix this?

To Reproduce

Steps to reproduce the behavior, please provide code snippets or a repository:

  1. Follow these steps
  2. Add your custom accessToken in the headers
  3. Try creating a subscription on one of your tables

Expected behavior

I want a realtime websocket connection to be created using my custom JWT and the payload of my custom JWT should be inserted into the claims column of 'realtime. subscription' table.

System information

w3b6x9 commented 1 year ago

@gouthamraj-r how did you set up self-host supabase?

gouthamraj-r commented 1 year ago

@w3b6x9 In my local docker setup like mentioned in this doc

itsgouthamraj commented 1 year ago

@w3b6x9 This seems to be a bug in supabase-js. I made some changes to it and now its working fine for me. Here's a PR for it - https://github.com/supabase/supabase-js/pull/704

w3b6x9 commented 1 year ago

See https://supabase.com/docs/guides/realtime/postgres-changes#custom-tokens.

gouthamraj-r commented 1 year ago

@w3b6x9 It doesn't solve my problem, still getting a connection error.

Screenshot 2023-02-05 at 11 43 19 PM

Sending custom jwt through headers causes this issue. I tried sending anon key and it works fine. (note: my custom jwt is also signed with the same supabase secret key as anon key)

gouthamraj-r commented 1 year ago

@w3b6x9 I'm able to fix this by doing this const supabase = createClient(supabaseUrl, supabaseAnonKey) supabase.realtime.accessToken = "<my custom jwt>"

w3b6x9 commented 1 year ago

See https://supabase.com/docs/guides/realtime/postgres-changes#custom-tokens.

This only works for hosted Supabase Realtime.

For self-hosters, you can use realtime-js client to pass your custom token on setup. If you're using Supabase's Kong setup found https://github.com/supabase/supabase/blob/90afbded45620633f0bd66cf873da917f2fb32a8/docker/docker-compose.yml, it will only accept either the anon or service_role tokens so you'll have to call realtime-js setAuth in order to pass your custom token. You can always tweak the Kong config for Realtime to suit your needs or use something else.

riderx commented 1 year ago

@w3b6x9 thanks a lot for the sharing. I came across to this issue on my side too. I made a CLI for my users, and they auth themselves to Supabase with an apikey system I made. You can see how I mad it here: https://gist.github.com/FelixZY/0aef530690458b381b8100afa19202c8?permalink_comment_id=4393151#gistcomment-4393151

That has worked super well, right now, I'm making an onboarding command who will watch a table change. So, I need to listen real time changes. And that don't work, so i did try to add the config suggested in https://supabase.com/docs/guides/realtime/postgres-changes#custom-tokens

export const createSupabaseClient = (apikey: string) => createClient<Database>(hostSupa, supaAnon, {
    global: {
        headers: {
            capgkey: apikey,
        }
    },
    realtime: {
        headers: {
            apikey: supaAnon,
        },
        params: {
            apikey,
            capgkey: apikey,
        },
    },
})

It seems my token is not present in RLS check with real-time request. it is doable? For the context here how i try to get it in the RLS

(current_setting('request.headers'::text, true))::json ->> 'capgkey'::text)

And this work with the rest of the SDK only in Real-time, it doesn't

w3b6x9 commented 1 year ago

@riderx since capgkey is your custom apikey you can extract something inside your claims by calling current_setting on request.jwt.claims and then extracting out your custom field and casting it into its type.

riderx commented 1 year ago

It would be like ?

((current_setting('request.jwt.claims'::text, true))::json ->> 'capgkey'::text)
riderx commented 1 year ago

Even with that don't work, I tried to find in the logs the issue but i could locate in the log my issue, do you know how i could inspect what's happening ? it feel a bit like a blackbox the RLS ^^

riderx commented 1 year ago

In the CLI my users use the anon key with the capgokey to check they right, @w3b6x9 you are sure jwt.claims will help me ? I have spend my day trying to understand this, if you are around to enlight me, i will be super glad.