powersync-ja / powersync-service

Other
123 stars 9 forks source link

SupabaseKeyCollctor keyOptions overrides `client_auth.audience` config #80

Closed yykcool closed 1 month ago

yykcool commented 2 months ago

Hi guys, i'm new to powersync, and have been spending some time to set it up both for my team's dev server, and for our local development (we're using supabase).

We're using Auth0 for our auth capabilities, and got it working by ways of using the supabase JWT secret to sign a custom token that we stuffed into the client as a authorization header.

I followed the setup docs for supabase and did the following for my powersync.yaml configs:


# Specify sync rules
sync_rules:
  path: sync-rules.yaml

# Client (application end user) authentication settings
client_auth:
  supabase: true

  # JWKS audience
  audience: ["supabase"] #matches the aud generated by generateSupabaseAccessToken (ctrl+shift+f)

i have the following function to obtain the access token:

//pages/api/auth/[...auth0].ts
/**
 * https://supabase.com/docs/guides/integrations/auth0
 *
 * Neither Supabase nor Auth0 allows for a custom signing secret to be set for
 * their JWT, and they both use different signing algorithms.
 *
 * Therefore, we need to extract the bits from Auth0's JWT and sign our own
 * JWT to send to Supabase.
 */
export function generateSupabaseAccessToken(claims: Claims) {
  const payload = {
    ...claims,
    role: "anon",
    aud: "supabase",
    exp: Math.floor(Date.now() / 1000) + TEN_DAYS_IN_SECONDS
  };

  if (process.env.SUPABASE_JWT_SECRET == null) {
    return null;
  }

  const accessToken = jwt.sign(payload, process.env.SUPABASE_JWT_SECRET);
  return accessToken;
}

after a few hours of head bashing, i went through the source code and realized that client_auth.audience gets overridden

['authenticated'] audience is declared here https://github.com/powersync-ja/powersync-service/blob/f6b678aea5d974e657a2d59bc4a9956185176435/packages/service-core/src/auth/SupabaseKeyCollector.ts#L15-L22

and is passed to KeyStore.ts here https://github.com/powersync-ja/powersync-service/blob/f6b678aea5d974e657a2d59bc4a9956185176435/packages/service-core/src/auth/KeyStore.ts#L103-L107

tracing it up, there's this bit https://github.com/powersync-ja/powersync-service/blob/f6b678aea5d974e657a2d59bc4a9956185176435/packages/service-core/src/auth/KeyStore.ts#L51-L57

i changed my token generator to the following and everything worked

export function generateSupabaseAccessToken(claims: Claims) {
  const payload = {
    ...claims,
    role: "anon",
    aud: ["supabase", "authenticated"], //powersync hard coded to only auth if authenticated is part of aud
    exp: Math.floor(Date.now() / 1000) + FIVE_DAYS_IN_SECONDS //lowered because powersync has lower exp limit
  };

is this expected behaviour? otherwise, it seems to be not mentioned in the documentation.

stevensJourney commented 1 month ago

It seems like the default audience of tokens generated by the Supabase JS SDK is authenticated. I verified this by inspecting a JWT from my local Supabase service.

I think the client_auth -> supabase: true setting assumes the token is generated by the Supabase SDK and thus assumes the audience.

If you are signing your own tokens with the Supabase JWT secret then one option is to not use the supabase: true setting and rather provide the secret and your custom audience(s) in the PowerSync config.

# Add the Supabase secret here
  jwks:
    keys:
      - alg: HS256
        k: [base64url formated secret]
        kty: oct
yykcool commented 1 month ago

That works, appreciate the prompt response!