cli icon indicating copy to clipboard operation
cli copied to clipboard

Supabase client with Secret Key is not compatible with local development

Open sapphire008 opened this issue 4 months ago • 8 comments

When developing locally, I created a supabase admin client with the Secret Key from supabase status

const SUPABASE_URL = "http://127.0.0.1:54321";
const SUPABASE_SECRET_KEY = "<local secret key>" // TODO

const supabase = createClient<Database>(SUPABASE_URL, SUPABASE_SECRET_KEY, {
  auth: {
    autoRefreshToken: false,
    persistSession: false
  }
});

and tried to add some mock users with supabase.auth.admin.createUser, I got the following error message:

āŒ Failed to connect to Supabase: invalid JWT: unable to parse or verify signature, token is malformed: token contains an invalid number of segments

However, if I change the to the old Service Role Key (which no longer prints in version 2.51.0), this works just fine.

Minimally reproducible example: test-supabase-local.zip

index.ts

#!/usr/bin/env tsx

import { createClient, User } from '@supabase/supabase-js';
import { Database } from './database.js';

// Configuration for local Supabase development
const SUPABASE_URL = "http://127.0.0.1:54321";
const SUPABASE_SECRET_KEY = "sb_secret_N7UND0UgjKTVK-Uodkm0Hg_xSvEMPvz"

// Create Supabase client with service role key for admin operations
const supabase = createClient<Database>(SUPABASE_URL, SUPABASE_SECRET_KEY, {
  auth: {
    autoRefreshToken: false,
    persistSession: false
  }
});

// Test users to insert
const testUsers = [
  {
    email: '[email protected]',
    password: 'TestPassword123!',
    user_metadata: {
      full_name: 'Test User One',
      username: 'testuser1'
    }
  },
  {
    email: '[email protected]',
    password: 'TestPassword123!',
    user_metadata: {
      full_name: 'Test User Two',
      username: 'testuser2'
    }
  },
  {
    email: '[email protected]',
    password: 'TestPassword123!',
    user_metadata: {
      full_name: 'Test User Three',
      username: 'testuser3'
    }
  }
];

async function insertTestUsers() {
  console.log('šŸš€ Starting test user insertion...');
  console.log(`šŸ“ Supabase URL: ${SUPABASE_URL}`);
  
  for (const [index, userData] of testUsers.entries()) {
    try {
      console.log(`\nšŸ‘¤ Creating user ${index + 1}: ${userData.email}`);
      
      // Use admin.createUser for creating users with passwords
      const { data, error } = await supabase.auth.admin.createUser({
        email: userData.email,
        password: userData.password,
        user_metadata: userData.user_metadata,
        email_confirm: true // Auto-confirm email for testing
      });

      if (error) {
        console.error(`āŒ Failed to create user ${userData.email}:`, error.message);
        continue;
      }

      if (data.user) {
        console.log(`āœ… Successfully created user: ${userData.email}`);
        console.log(`   User ID: ${data.user.id}`);
        console.log(`   Email confirmed: ${data.user.email_confirmed_at ? 'Yes' : 'No'}`);
      }

    } catch (error) {
      console.error(`šŸ’„ Unexpected error creating user ${userData.email}:`, error);
    }
  }

  console.log('\nšŸŽ‰ Test user insertion completed!');
}

async function listExistingUsers() {
  try {
    console.log('\nšŸ“‹ Listing existing users...');
    
    const { data, error } = await supabase.auth.admin.listUsers();
    
    if (error) {
      console.error('āŒ Failed to list users:', error.message);
      return;
    }

    console.log(`Found ${data.users.length} users:`);
    data.users.forEach((user: User, index: number) => {
      console.log(`  ${index + 1}. ${user.email} (ID: ${user.id})`);
    });

  } catch (error) {
    console.error('šŸ’„ Unexpected error listing users:', error);
  }
}

async function main() {
  console.log('šŸ”§ Supabase Test User Insertion Script');
  console.log('=====================================');

  // Check if we can connect to Supabase
  try {
    const { data, error } = await supabase.auth.admin.listUsers({ page: 1, perPage: 1 });
    if (error) {
      console.error('āŒ Failed to connect to Supabase:', error.message);
      console.log('\nšŸ’” Make sure:');
      console.log('   1. Supabase is running locally (supabase start)');
      console.log('   2. VITE_SUPABASE_URL is set correctly');
      console.log('   3. SUPABASE_SERVICE_ROLE_KEY is set correctly');
      process.exit(1);
    }
    console.log('āœ… Successfully connected to Supabase');
  } catch (error) {
    console.error('šŸ’„ Connection error:', error);
    process.exit(1);
  }

  // List existing users first
  await listExistingUsers();

  // Insert test users
  await insertTestUsers();

  // List users again to show the results
  await listExistingUsers();
}

// Run the script
if (import.meta.url === `file://${process.argv[1]}`) {
  main().catch(console.error);
}
  • I also tried using Python client and got the same error.
  • I am on Supabase CLI 2.51.0 which is the latest version at the time of writing.
  • I have tried to delete everything and reinstall but it didn't make a difference.
  • In the previous thread (currently locked) #29260, local development compatibility has been fixed in a previous release, but I am not sure it specifically address the case I mentioned above.
  • Is there any settings that I need to configure to allow the server to accept the new secret key?

sapphire008 avatar Oct 26 '25 03:10 sapphire008

Linking original discussion thread: https://github.com/orgs/supabase/discussions/39605. Please let me know if this is the appropriate place to open this issue or help me migrate it to an appropriate place.

sapphire008 avatar Oct 26 '25 03:10 sapphire008

Running into the same issue, old local service role key works fine but not the new secret key

rottbers avatar Oct 27 '25 12:10 rottbers

Hi all, Is this happening for you on cli or self-hosted?

Hallidayo avatar Nov 01 '25 16:11 Hallidayo

For me, it's in the local dev environment.

Is there anything I can do to help resolve this issue?

matleh avatar Nov 01 '25 20:11 matleh

@Hallidayo this is on cli, local dev enrichment.

sapphire008 avatar Nov 02 '25 05:11 sapphire008

@sweatybridge

aantti avatar Nov 03 '25 13:11 aantti

Transferred over from the supabase/supabase repo.

Hallidayo avatar Nov 26 '25 08:11 Hallidayo

Can you try the latest cli release? I believe it's fixed by https://github.com/supabase/cli/pull/4489

npx supabase@latest start

sweatybridge avatar Nov 26 '25 09:11 sweatybridge

I'm encountering this issue locally with Supabase CLI v2.72.0. My setup has gotrue issuing ES256 tokens for users, but internal service tokens (e.g., for Edge Function workers) remain HS256.

The Problem

The Edge Runtime crashes with: TypeError: Key for the ES256 algorithm must be of type CryptoKey. Received an instance of Uint8Array

This happens because the runtime defaults to initializing with the symmetric JWT_SECRET (Uint8Array), but receives an ES256 token.

Failed Attempt: Explicit signing_keys.json

I tried to fix this by explicitly configuring signing_keys.json in config.toml to provide the proper ES256 JWK.

  1. ES256 Only: If I provide only the ES256 key, user auth works, but internal services verification fails (e.g., database webhooks, cron jobs) because they use the default HS256 service key, which is no longer in the keyring (replaced by the JSON file).
  2. Mixed Keys (ES256 + HS256): I attempted to include both keys in signing_keys.json to support both users and services.
    [
      { "alg": "HS256", "k": "...", ... },
      { "alg": "ES256", ... }
    ]
    
    

Result: The CLI refuses to start with the error:

failed to decode signing keys: failed to parse response body: must be one of [RS256 ES256]

Conclusion It seems seemingly impossible to configure a "Mixed Environment" locally via signing_keys.json because the CLI validator rejects HS256 keys in that file. Currently, the only workaround is to disable verify_jwt in config.toml and manually verify tokens inside the Edge Function code.

obigroup avatar Jan 09 '26 11:01 obigroup

so, is this getting fixed? Still happening

oscadev avatar Jan 11 '26 17:01 oscadev

Still happening after upgrading to lates cli

Pjaijai avatar Jan 19 '26 06:01 Pjaijai

Also #4726 šŸ¤”

aantti avatar Jan 19 '26 09:01 aantti

I wanted to start a new fresh project and spent hours to try to make it work because of this silly bug... I will go to another backend platform

danbraik avatar Jan 21 '26 09:01 danbraik

How the hell is this acceptable? Completely broken the local setup...

WhoAteDaCake avatar Jan 21 '26 20:01 WhoAteDaCake

supabase status -o env

The secret key provided in supabase status command is NOT the actual secret key.

Use the above command to output the raw status and you will see the JWT.

Thank me later.

Dontblink782 avatar Jan 24 '26 00:01 Dontblink782

supabase status -o env

This gives me a new SERVICE_ROLE_KEY for each call, caused by an updated exp timestamp in the payload.

stefan-girlich avatar Jan 24 '26 10:01 stefan-girlich

This is described in https://github.com/supabase/cli/issues/4751 - there seems to be a problem with asymmetric JWT api keys status indeed.

Also, to clarify the current secrets and keys configuration in the latest versions of CLI after it's switched recently to asymmetric "ES256" keys by default:

  1. New opaque api keys (sb_publishable_ and sb_secret_) are hardcoded in CLI
  2. Asymmetric keypair is also hardcoded
  3. Auth and other services are now configured to use asymmetric encryption keys by default (you can check it via docker inspect for individual containers - look for something like _JWT_KEYS, JWT_JWKS, etc.)
  4. There's still ANON_KEY and SERVICE_ROLE_KEY env-vars, but these are now ES256-signed JWTs (not the old HS256 symmetric ones). They're signed with the hardcoded asymmetric private key. Both are passed as part of container configuration to Functions and Storage šŸ¤”
  5. These updated asymmetric JWT api keys are generated on each supabase start - and although the asymmetric keypair is hardcoded, the keys keep changing because a new timestamp is used on every start.
  6. In Kong configuration there's a "translation" from sb_ api keys to role-containing asymmetric JWT api keys from # 4 above. You can check what's happening there via docker exec -it supabase_kong_PROJ_NAME grep "sb_secret" /home/kong/kong.yml | head -1. Requests with minted user sessions asymmetric JWT tokens are just passed through.
  7. The JWT_SECRET still exists for backward compatibility with HS256 tokens, but there's a misconfiguration at the CLI-vs-Auth level - currently being investigated.
  8. It is not advisable to extract and use the asymmetric JWT api keys in your application - those aren't supposed to be directly used for authentication/authorization. As I understand, the expectation has been that users start switching their application architectures to using the "new API keys" - the sb_ keys. This makes supabase status -o env a bit obsolete with regard to anon and service role api keys.

aantti avatar Jan 24 '26 12:01 aantti

facing the same issue but for local edge functions

im facing

2026-01-24T19:42:23.173836546Z TypeError: Key for the ES256 algorithm must be of type CryptoKey. Received an instance of Uint8Array
2026-01-24T19:42:23.173877463Z     at asymmetricTypeCheck (https://deno.land/x/[email protected]/lib/check_key_type.ts:14:11)
2026-01-24T19:42:23.173880213Z     at checkKeyType (https://deno.land/x/[email protected]/lib/check_key_type.ts:39:5)
2026-01-24T19:42:23.173881296Z     at flattenedVerify (https://deno.land/x/[email protected]/jws/flattened/verify.ts:78:3)
2026-01-24T19:42:23.173882296Z     at compactVerify (https://deno.land/x/[email protected]/jws/compact/verify.ts:15:26)
2026-01-24T19:42:23.173883255Z     at Module.jwtVerify (https://deno.land/x/[email protected]/jwt/verify.ts:5:26)
2026-01-24T19:42:23.173884296Z     at verifyJWT (file:///var/tmp/sb-compile-edge-runtime/root/index.ts:95:16)
2026-01-24T19:42:23.173885296Z     at Object.handler (file:///var/tmp/sb-compile-edge-runtime/root/index.ts:140:34)
2026-01-24T19:42:23.173886213Z     at mapped (ext:runtime/http.js:231:42)
2026-01-24T19:42:23.173887130Z     at respond (ext:runtime/http.js:339:14)
2026-01-24T19:42:23.173888046Z     at handleHttp (ext:runtime/http.js:160:9)

roo12312 avatar Jan 24 '26 19:01 roo12312

Hi there, we've released v2.74.4 to address this issue with local dev. In summary, you should be able to

  1. Continue using legacy anon and service_role keys with supabase-js locally. These legacy keys are shown with supabase status -o env command.
const supabase = createClient<Database>(
  "http://127.0.0.1:54321",
  "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
);
  1. Opt into the new publishable and secret keys that are shown under supabase start command. These are drop-in replacements for the legacy keys you are using in supabase-js.
const supabase = createClient<Database>(
  "http://127.0.0.1:54321",
  "sb_secret_...",
);

With both keys enabled, you should be able to test the transition to new api keys fully locally. Once you are done testing, go to supabase dashboard and enable the new api keys. Your app should work seamlessly on next deploy.

For differences between legacy and new api keys, see also: https://github.com/orgs/supabase/discussions/29260

sweatybridge avatar Jan 30 '26 16:01 sweatybridge