-
-
Notifications
You must be signed in to change notification settings - Fork 341
v2.0.0 regression: Custom JWT (particularly in realtime Channels) #553
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
What I was hoping for was a SignInWithToken(...) api As is it feels daunting reaching in to protected fields in multiple files to achieve this. |
Any news on this? It's a regression.. Any way to get the devs attention? |
I'm looking for something like this. It'll solve so many problems I'm having right now. |
This was actually due to an issue where the Realtime client would always default to using the anon token. I released a change where Realtime client will use the global headers Authorization bearer token if present: https://github.com/supabase/supabase-js/releases/tag/v2.7.0 |
I do want to mention that if your custom token is short-lived and you're refreshing it yourself you'll still have to call Realtime's |
Ha..! Thanks.. I wonder if the issue of a customJWT refresh method is on the todo's ? It's all documented on supabase/auth-js#701 along with patches to unprotect the applicable class properties:
which have to be updated in addition to calling setAuth again when the JWT expires.. |
After taking a closer look the issue is on the API gateway. It only accepts Supabase signed tokens (i.e. I just added a section to Realtime docs on how to pass in custom tokens: https://supabase.com/docs/guides/realtime/postgres-changes#custom-tokens. |
@w3b6x9 We've just been testing this and want to highlight that the docs are slightly off. They say to pass the Supabase key into headers and the custom JWT into params, whereas it's actually the other way around. What worked for us is:
Whereas the docs say:
Note that the description of the Supabase anon key in the docs is also confusing: "The Other than this needing to be the |
@w3b6x9 I'm migrating to Supabase from Firebase. I'm keeping firebase auth for now since Supabase doesn't have anonymous auth yet. To summarize and clarify the issues based on the above, here's what I currently do:
const db = createClient<Database>(creds.url, creds.key, {
auth: authOpts,
global: {
headers: {
authorization: `Bearer ${key}`, // my custom token
},
},
realtime: {
headers: {
apikey: key, // my custom token returned from my edge fn --docs have it backwards
},
params: {
apikey: creds.key, // the supabase anon key
},
},
}) This method works up to the point where I need to refresh the user's token, which I want to do whenever their firebase token gets refreshed. I can call my edge fn to verify the new firebase token and generate a new supabase token, but then as @LuisAngelVzz pointed out there isn't a great way to set this new token in the existing client. For now I will use a workaround like was posted above. I think there are two things needed to solve these problems:
Are either of these things currently planned to be implemented? If so, when? I hope this effectively summarizes the issue. IMO this is likely to be a problem faced by almost everyone attempting to migrate from Firebse so I hope it can get bumped up to a higher priority by the Supabase team. |
Here is how I made my custom JWT work with RLS + REALTIME.I debugged this for a long time and found out that there was an issue in supabase-js package (The actual issue can be in realtime-js or go-true.js packages). Issue: Workaround solution:
Then pass the
If you want to patch it locally, you can use
|
For reference here is how I'm updating my supabase client whenever my firestore token gets refreshed. /**
* Hax to set new auth tokens on the supabase client when we generate a new token from a firebase token
* See these issues
* https://github.com/supabase/gotrue-js/issues/701
* https://github.com/supabase/supabase-js/issues/553
*/
export const refreshSupabaseTokenFromFirebaseToken = async (
db: TaskHeroSupabaseClient | null,
firebaseToken: string
) => {
const key = await getTokenFromFirebaseToken(firebaseToken)
if (!db) {
throw `db not initialized yet`
}
db.headers.Authorization = `Bearer ${key}`
db.realtime.setAuth(key)
//@ts-ignore this is set as a protected field but we need to modify it to work around lack of custom token refresh
db.realtime.headers.apikey = `Bearer ${key}`
//@ts-ignore this is set as a protected field but we need to modify it to work around lack of custom token refresh
db.auth.headers.Authorization = `Bearer ${key}`
//@ts-ignore this is set as a protected field but we need to modify it to work around lack of custom token refresh
db.rest.headers.Authorization = `Bearer ${key}`
return key
} You can use It's also worth noting if you want to use firebase auth that firebase-js |
As someone attempting to use a third-party auth, Clerk in this case, here are my thoughts about the direction of implementation for custom tokens. Bear with me here, I'm no master developer, so these are just my thoughts. There seems to be a push to just create a new supabase client for every call and load the token at that time. Here are the things that I see that are problematic with this approach:
Non-approached based notes of the current state of custom tokens:
Design Opinion on Ideal solution: That's just my 2 cents on that. I apologize in advance if that's a dumb idea, I'm not super well versed in the security considerations of JWT authentication. Unfortunately, because I am planning for a production app, I am not comfortable applying the very innovative hacks that others have put together here form @Mukhammadali and @evelant . So I am now left with a choice of losing my chosen Auth implementation, losing supabase, or coming up with some hyper custom implementation using a self-designed middleware. Any thoughts @w3b6x9 @kangmingtay @silentworks ? |
@rdylina After digging into it a little bit it seems the issue, at least for me, isn't RLS specifically. Instead the problem is that realtime postgres change listeners are authenticated as Requests from postgres-js work fine with the token, they get the proper @w3b6x9 having realtime work with a custom token is a requirement for us to switch from firebase to supabase. We can't migrate our auth yet so we have to continue using firebase auth which means we need to have custom tokens working or we can't migrate to Supabase. Any further pointers or input on this issue would be greatly appreciated! edit: looking at the I also see this in the docker logs for the local realtime container when I try to start a subscription without giving
|
OK I seem to have gotten it working. Not sure why this is working, it's not even close to what the docs suggest. If I create a client like this -- note I commented out any realtime config const db = createClient<Database>(creds.url, creds.key, {
auth: authOpts,
global: {
headers: {
authorization: `Bearer ${key}`,
},
},
// realtime: {
// // headers: {
// // apikey: `Bearer ${key}`,
// // },
// params: {
// // apikey: creds.key,
// accessToken: `Bearer ${key}`,
// },
// },
})
//@ts-ignore - this is a private method but we must call it to make auth work on realtime channels with our custom token
db.realtime.setAuth(key) then call I'll need to test this some more (specifically token refresh) but this may be a good enough workaround until the problems with custom tokens get fixed. |
@evelant I suspected that realtime wasn't accepting the token but didn't have the depth of expertise to verify it, good to know that is what is actually happening. Still very much dislike that we're overriding a private method to set the refreshed token. Unfortunately, I'm still in a spot where we can't properly update the regular client token without overrides as well. =( Update: |
After tooling around all evening with this, here is what I've come up with as an ugly work around. I'm using react query to perform interval updates of the token and to account for failures, refetch on mount, and window refocus, etc. const { data, error } = useQuery({
queryKey: ["getToken"],
queryFn: () =>
getToken({
template: "supabase-nextjs-boilerplate",
}),
enabled: isSignedIn,
refetchInterval: 50 * 1000,
refetchIntervalInBackground: true,
onSuccess: (data) => {
if (data) updateToken(data);
},
});
function updateToken(newToken: string) {
if (newToken) {
const now = new Date().getTime();
const exp = parseJwt(newToken).exp * 1000;
const secondsToExpiration = exp - now;
supabaseClient?.functions.setAuth(newToken);
// @ts-ignore this is set as a private method but we need to modify it to work around lack of custom token refresh
supabaseClient?.realtime.setAuth(newToken);
//@ts-ignore this is set as a protected field but we need to modify it to work around lack of custom token refresh
supabaseClient.auth.headers.Authorization = `Bearer ${newToken}`;
//@ts-ignore this is set as a protected field but we need to modify it to work around lack of custom token refresh
supabaseClient.rest.headers.Authorization = `Bearer ${newToken}`;
}
} The expiration calculations were just for debugging purposes to ensure I was receiving a new token. |
@rdylina That looks like a good workaround. Pretty much identical to what I'm doing. I'm going to test it further today and will report back here if I find any quirks in this method. @w3b6x9 doesn't seem to be responding here but I think they're following along, they've removed the |
@rdylina Frustratingly this workaround doesn't seem to actually work. Once I've refreshed my token I still get invalid token failures. @laktek @hf @soedirgo @J0 Could you provide some input on this issue please? Custom tokens + refresh is the final blocker for my team to finish migrating from Firebase to Supabase. |
Checkout my workaround. Have been using it for months. |
Hmm, I'm already setting those fields on token refresh. As far as I can tell this should cover all the bases, but I'm still running into issues. export const refreshSupabaseTokenFromFirebaseToken = async (
db: MySupabaseClient | null,
firebaseToken: string
) => {
const key = await getTokenFromFirebaseToken(firebaseToken)
if (!db) {
throw `db not initialized yet`
}
//@ts-ignore protected field
db.headers.Authorization = `Bearer ${key}`
//@ts-ignore protected field
db.realtime.setAuth(key)
db.functions.setAuth(key)
//@ts-ignore this is set as a protected field but we need to modify it to work around lack of custom token refresh
db.auth.headers.Authorization = `Bearer ${key}`
//@ts-ignore this is set as a protected field but we need to modify it to work around lack of custom token refresh
db.rest.headers.Authorization = `Bearer ${key}`
return key
} I'm going to run through all my token refresh code again to check for any silly mistakes. |
Very curious indeed. My workaround has been working so far the last couple of days. Obviously not a 1 to 1 comparison since we're getting our tokens from different places. Hmmm.. |
Ok, my bad, it was a silly mistake, essentially a typo. I had |
That's weird typically headers are case-insensitive. I'm glad you fixed it! 🎉 |
Hello @kangmingtay , Thank you for the reply. My thoughts are below.
Currently, I am solving for this by making my token longer lived (120 seconds) and then caching it and only requesting updated tokens based on the current token's expiration minus 5 seconds. Not the most elegant of solutions but it beats polling for every request.
My concern regarding memory wasn't really the maximum point in time utilization but the pure machine overhead of allocating the memory and then deallocating and/or garbage collecting. I come from a background in lower-level machine languages, and, for better or worse, it makes me cringe how flippant we as a group are with higher-level languages in the way we treat compute overhead. It's just my natural instinct to minimize overhead and maximize performant design patterns. 🙈 Maybe I'm over the top on this topic.
Thank you for the clarification. I wasn't sure if any of the REST apis support batched queries or if that was a thing that was ever on the roadmap. I super appreciate you taking the time to write out such a detailed response. |
@rdylina welcome to the world of javascript 🙃 on a more serious note, we chose to go with that design pattern of calling
well, this is already possible if you write your SQL statements in a function and use the |
Hey @rdylina! So we set it up so that Realtime's settings would overwrite global's and that includes headers: supabase-js/src/SupabaseClient.ts Line 119 in fdb7bf5
What do you mean when you write that it doesn't work for you? Are you able to subscribe at all? Can you share your setup? |
Annoyingly I'm still having problems with updating the client's headers when I refresh my custom token. My code posted above worked fine against the local supabase instance in docker but is failing against my live staging instance. I verified that I am getting a correctly signed updated token but unfortunately postgrest and edge function requests are failing after I attempt to update the client. @w3b6x9 @hf @kangmingtay Could you please provide an example of how you would update a custom token on an existing client instance? I'm really struggling with this and it's the last major piece of the puzzle necessary to transition my app to Supabase. We can't go live with the switch to Supabase until token refresh is working reliably. |
Hi @evelant, you can't update a custom token on an existing client instance, you will need to create a new client and set it like this: const supabase = createClient(SUPABASE_URL, SUPABASE_KEY, {
...clientOptions,
global: {
headers: {
Authorization: `Bearer ${CUSTOM_ACCESS_TOKEN_JWT}`,
},
},
}); Do note that your When your |
@kangmingtay I already sign my custom token with JWT_SECRET and it works fine per my above comments. Creating a new client on token refresh poses a problem because I have realtime subscriptions running at the time when the token refresh occurs. How does the client handle that internally on token refresh? I'd like to avoid having to stop all listeners, create a new client, then re-create all of the listeners and handle the case where they missed messages during that transition. That would be quite a headache to manage just to work around refreshing a token. |
@w3b6x9 I just double then triple checked to confirm, and it seems that your post above and the documentation is indeed backwards about the keys, at least during local development. realtime: {
headers: {
apikey: creds.key, //--supabase anon key
},
params: {
apikey: `Bearer ${key}` //-- my JWT signed with JWT_SECRET
},
}, that, as recommended by the docs does not work, it results in the following 401 unauthorized in kong logs
However if I switch it around and put my signed custom JWT into the headers, everything works perfectly, my clients connect and get realtime updates from postgres. realtime: {
headers: {
apikey: `Bearer ${key}` //-- my JWT signed with JWT_SECRET
},
params: {
apikey: creds.key,// --supabase anon key
},
}, Also a heads up, realtime is broken in supabase cli Can anybody else confirm and reproduce this? |
@evelant I recommend creating two Supabase clients where the first client is the one you use for everything except Realtime so you can re-create it whenever you have a refreshed token. The second client can be used exclusively for Realtime activity. You create the client, pass in the initial custom token, and then whenever you have the refreshed token, just call secondClient.realtime.setAuth('new-token'). This will prevent having to sever Realtime's socket connection. |
@evelant the instructions I provided above and the ones found in the Supabase docs are confirmed for hosted Realtime. CLI local development is a bit different b/c it's still using Kong while hosted version has transitioned over to Cloudflare for the API gateway.
@evelant thanks, will take a look at CLI local development and get back to everyone shortly.
@evelant you're right, this was an issue but looks like it's now fixed in CLI v1.49.3. |
@evelant I just looked at the way you set it up for local development:
And it works b/c Kong is verifying params.apikey but the problem is that Realtime is forwarded that key as well so if you check your CLI did recently merge in a change where you can pass in the initial custom token when spinning up the CLI. See PR: supabase/cli#947. For example, you can pass in the custom token for role Then you can pass in the refreshed token by calling |
@w3b6x9 at least locally realtime is not forwarded the anon key in this case. As for the production side it appears that manually changing the headers on the client when I refresh my token works fine. Everything continues to work, except for realtime. After calling |
@w3b6x9 Let me clarify exactly what I'm doing and exactly the behavior I'm seeing so hopefully there isn't any confusion. At a high level, my app does this:
export const setupDbConnection = async (
localStorageInstance: { getItem: any; setItem: any; removeItem: any },
firebaseToken: string
) => {
const key = await getTokenFromFirebaseToken(firebaseToken)
const authOpts = {
storage: localStorageInstance,
autoRefreshToken: false,
persistSession: false,
detectSessionInUrl: false,
}
const db = createClient<Database>(creds.url, creds.key, {
auth: authOpts,
global: {
headers: {
Authorization: `Bearer ${key}`, // custom token with 'authenticated' role signed with jwt secret
},
},
realtime: {
headers: {
apikey: `Bearer ${key}`, // custom token with 'authenticated' role signed with jwt secret
},
params: {
apikey: creds.key, // supabase anon key
},
},
})
db.realtime.setAuth(key)
return db
}
export const refreshSupabaseTokenFromFirebaseToken = async (
db: TaskHeroSupabaseClient | null,
firebaseToken: string
) => {
if (!db) {
throw `db not initialized yet`
}
const key = await getTokenFromFirebaseToken(firebaseToken)
//@ts-ignore protected field
db.headers.Authorization = `Bearer ${key}`
//@ts-ignore
db.auth.headers.Authorization = `Bearer ${key}`
//@ts-ignore
db.rest.headers.Authorization = `Bearer ${key}`
db.realtime.setAuth(key)
if (db.realtime.params) {
db.realtime.params.apikey = `Bearer ${key}`
}
db.functions.setAuth(key)
return key
} After doing this everything still works in local development. In production deployment everything works except realtime which no longer receives updates. To summarize the problems:
I hope that clarifies exactly what's happening for me and helps track down the problem. Please ask any questions if you need clarification. |
I think I've finally got it working. My example code above had an issue, had to remove
since that param doesn't change on token refresh. It's always the supabase anon token. With this I'm finally finding that realtime stays connected and requests from the client continue to succeed without re-creating the client. I'm still testing but hopefully I've got it this time. It would be nice if supabase-js could get a built-in |
hey @evelant, apologies for the late reply as the team was quite busy with launch week! just to clarify, i'm assuming that you'd want db.headers.Authorization = `Bearer ${key}`
db.auth.headers.Authorization = `Bearer ${key}`
db.rest.headers.Authorization = `Bearer ${key}`
db.realtime.setAuth(key)
db.functions.setAuth(key) |
@kangmingtay Based on your comment, I made the realtime work with custom jwt. Here is how I handle it: export const getSupabaseRealtime = (exchangeToken: string) => {
const intialOptions = {
realtime: {
headers: {
apikey: `Bearer ${exchangeToken}`,
},
params: {
apikey: ConfigService.getPropertyAnonKey(),
},
},
};
const realtimeClient = createClient(
ConfigService.getPropertySupabaseUrl(),
ConfigService.getPropertyAnonKey(),
intialOptions
);
realtimeClient.realtime.setAuth(exchangeToken);
return realtimeClient;
}; |
I think I've followed all of what has worked for @evelant (set I'm getting this strange error response back from the realtime websocket: My JWT looks like this: {
"role": "authenticated",
"app_metadata": {
// some metadata here
},
"aud": "authenticated",
"iat": 1685701508,
"exp": 1685705108
} And here's the first message sent to the websocket: {"topic":"realtime:xxx","event":"phx_join","payload":{"config":{"broadcast":{"ack":false,"self":false},"presence":{"key":""},"postgres_changes":[{"event":"*","schema":"public","table":"mytable"}]},"access_token":"myjwt"},"ref":"1","join_ref":"1"} My users don't correlate to users in Supabase, so I don't include a Anyone got an idea what's going on here? Would appreciate any help! I'm at a loss. |
@hmnd Sorry but I'm not sure what's going on there. FYI I would double check:
CREATE POLICY "Enable full access to rows owned by authenticated user" ON "public"."my_table"
AS PERMISSIVE FOR ALL
TO authenticated
USING (auth.jwt() ->> 'sub' = "user_id");
|
@evelant wow thanks for the fast reply! That's good to know about I believe everything is configured correctly, but I'll double check. Maybe I'll try with a regular supabase jwt too to try to narrow down what that error could mean... |
@evelant spent half a day yesterday trying to debug this and just found that I was missing the |
I decided to write a whole medium post for avoiding you waste time in the future: https://liviogamassia.medium.com/using-supabase-rls-with-firebase-auth-custom-auth-provider-357eaad9c70f |
Hey all ... thanks for fumbling through the black box here. Especially @LivioGama for the full write up. I needed to figure this out too. I can confirm, at least for Realtime, the only thing you need to do is call Couple updates:
I'm also trying to get something official up in our docs wrt using your own JWTs across the whole platform. Not exactly sure why we don't have that yet. |
@chasers just curious if you ever got around to documenting this more fully or if that's been put on the roadmap? I'm sure it would be very beneficial for future users trying to stumble through this. |
I did! See: https://supabase.com/docs/guides/realtime/postgres-changes#custom-tokens And you can use the Inspector with a custom JWT here: https://realtime.supabase.com/inspector/new Gonna close this one now! |
For other people (especially Clerk auth users) using a custom JWT, here are two patterns I'm using. Feel free to give feedback if you have a better way:
import { SupabaseClient, createClient } from '@supabase/supabase-js'
let supabaseClientPool: Record<string, SupabaseClient> = {}
export const supabaseJwtClient = (accessToken: string) => {
if (supabaseClientPool[accessToken]) {
return supabaseClientPool[accessToken]
}
purgePoolOnBrowser()
const client = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
global: { headers: { Authorization: `Bearer ${accessToken}` } },
},
)
client.realtime.setAuth(accessToken)
supabaseClientPool[accessToken] = client
return client
}
const purgePoolOnBrowser = () => {
if (typeof window !== 'undefined') {
supabaseClientPool = {}
}
}
import { useCallback, useEffect, useState } from 'react'
import { supabaseJwtClient } from '@/lib/supabase/supabaseJwtClient'
import { useJwt } from '@/lib/auth/useJwt'
import { devLog } from '../logging/devLog'
import {
REALTIME_POSTGRES_CHANGES_LISTEN_EVENT,
REALTIME_SUBSCRIBE_STATES,
RealtimeChannel,
} from '@supabase/supabase-js'
export const useRealtimeTable = <T extends { id: string }>(
tableName: string,
) => {
const { getJwt, getJwtExpiration } = useJwt()
const [records, setRecords] = useState<T[]>([])
const fetchRecords = useCallback(async () => {
const supabase = supabaseJwtClient(await getJwt())
const { data, error } = await supabase.from(tableName).select('*')
if (error) {
devLog(`Error fetching ${tableName}:`, error)
} else {
setRecords(data || [])
}
}, [tableName, getJwt])
const subscribeToTable = useCallback(async () => {
let supabase = supabaseJwtClient(await getJwt())
let lastChannelState: REALTIME_SUBSCRIBE_STATES | null = null
let channel: RealtimeChannel | null = null
const subscribe = () => {
channel = supabase
.channel(`${tableName}-changes`)
.on(
'postgres_changes',
{
event: REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.ALL,
schema: 'public',
table: tableName,
},
(payload: any) => {
if (
payload.eventType ===
REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.UPDATE
) {
setRecords((prevRecords) =>
prevRecords.map((record) =>
record.id === payload.new.id ? payload.new : record,
),
)
}
if (
payload.eventType ===
REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.INSERT
) {
setRecords((prevRecords) => [...prevRecords, payload.new])
}
if (
payload.eventType ===
REALTIME_POSTGRES_CHANGES_LISTEN_EVENT.DELETE
) {
setRecords((prevRecords) =>
prevRecords.filter((record) => record.id !== payload.old.id),
)
}
},
)
.subscribe(async (status, error) => {
devLog(`[${tableName}]: `, status)
if (
status === REALTIME_SUBSCRIBE_STATES.CLOSED &&
lastChannelState === REALTIME_SUBSCRIBE_STATES.SUBSCRIBED
) {
devLog('Channel closed (e.g. JWT expiration), resubscribing...')
await refreshToken()
cleanup()
subscribe()
}
if (error) {
console.error(`[${tableName}] Real-time channel error:`, error)
}
lastChannelState = status as REALTIME_SUBSCRIBE_STATES
})
}
const cleanup = () => {
if (channel) supabase.removeChannel(channel)
}
const refreshToken = async () => {
const token = await getJwt()
supabase.realtime.setAuth(token)
}
const automaticallyRefreshJwt = async () => {
const expiration = await getJwtExpiration()
if (expiration) {
const currentTime = Math.floor(Date.now() / 1000)
// Refresh 5 seconds before expiration
const timeUntilExpiration = expiration - currentTime - 5
if (timeUntilExpiration > 0) {
setTimeout(async () => {
await refreshToken()
automaticallyRefreshJwt() // Set up the next refresh
}, timeUntilExpiration * 1000)
}
}
}
window.addEventListener('beforeunload', cleanup)
subscribe()
await automaticallyRefreshJwt()
// Clean up on unmount
return () => {
window.removeEventListener('beforeunload', cleanup)
cleanup()
}
}, [tableName, getJwt, getJwtExpiration])
useEffect(() => {
fetchRecords()
subscribeToTable()
}, [tableName, fetchRecords, subscribeToTable])
return { records }
} And just for completeness, my JWT code (for Clerk auth): import { useCallback } from 'react'
import { useAuth } from '@clerk/nextjs'
import jwtDecode from 'jsonwebtoken'
export const useJwt = () => {
const { getToken } = useAuth()
const getJwt = useCallback(async (): Promise<string> => {
const jwt = await getToken({ template: 'supabase' })
if (!jwt) throw new Error('No JWT found')
return jwt
}, [getToken])
const getJwtExpiration = useCallback(async (): Promise<number | null> => {
const jwt = await getJwt()
try {
const decoded = jwtDecode.decode(jwt) as { exp: number } | null
return decoded?.exp || null
} catch (error) {
console.error('Failed to decode JWT', error)
return null
}
}, [getJwt])
return { getJwt, getJwtExpiration }
} |
This was majorly helpful thank you so much |
Bug report
Description
Using a custom JWT was a feature in v1 :
https://supabase.com/docs/reference/javascript/auth-setauth
Per comment:
supabase/auth-js#340 (comment)
what we did was establish the custom header and it works for creating the client.
However, it's not working with realtime channels. Upon inspection, the problem is that the internal initialization isn't setting up RealtimeClient's setAuth method:
https://github.com/supabase/realtime#realtime-rls
which in turn causes the web socket to not send the JWT on the messages and heartbeats..
The result is that channels fail the RLS.
A second problem is with setting up a new valid JWT when the previous one expired: there's no way to do it. Not in the supabase client and much less in the realtime client.
To Reproduce
feat: remove deprecated session methods auth-js#340 (comment)
4.a. Let 2 minutes go by and try to make any call to supabase using supabaseClient. Access is denied because the JWT has expired. Try to update the JWT -> there's no way to do it. Would have to create a new client but that defeats the whole thing.
4.b. Create a channel subscription -> 'fails' by not providing access to rows to which the user has access acording to the policy)
Upon inspection of the websocket, as it was expected, the messages and heartbeats don't include the custom JWT.. Why whould they? We've only set the header /directly/ and that's it..
Workaround
What we've done is changing from protected to public the supabase client's class elements:
"headers" in SupabaseClient
"realtime" in GoTrueClient
and we're calling
a) supabaseClient.realtime.setAuth(JWT)
b) supabase.auth.headers.Authorization =
Bearer ${JWT}
;with the customToken..
That keeps everything working..
Tried forking and creating an updateJWT method, but realized we're not very familiar with the modularization philosophy of the project and was most likely being both overkill about it. Also, were falling short because an alternate method is quite likely required for the initialization, since using the headers option in the createClient doesn't impact the realtime client.
The text was updated successfully, but these errors were encountered: