Skip to content

fix(shared): Generate publishable keys with unpadded Base64 encoding to match backend output#8400

Merged
wobsoriano merged 3 commits intomainfrom
kevin/fix-buildPublishableKey
Apr 25, 2026
Merged

fix(shared): Generate publishable keys with unpadded Base64 encoding to match backend output#8400
wobsoriano merged 3 commits intomainfrom
kevin/fix-buildPublishableKey

Conversation

@thiskevinwang
Copy link
Copy Markdown
Member

  • fix(buildPublishableKey): build unpadded key
  • chore: changeset

Description

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 25, 2026

🦋 Changeset detected

Latest commit: 5d4f79b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 20 packages
Name Type
@clerk/shared Patch
@clerk/astro Patch
@clerk/backend Patch
@clerk/chrome-extension Patch
@clerk/clerk-js Patch
@clerk/expo-passkeys Patch
@clerk/expo Patch
@clerk/express Patch
@clerk/fastify Patch
@clerk/hono Patch
@clerk/localizations Patch
@clerk/msw Patch
@clerk/nextjs Patch
@clerk/nuxt Patch
@clerk/react-router Patch
@clerk/react Patch
@clerk/tanstack-react-start Patch
@clerk/testing Patch
@clerk/ui Patch
@clerk/vue Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 25, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Apr 25, 2026 2:25am

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 25, 2026

📝 Walkthrough

Walkthrough

The pull request updates the @clerk/shared package to generate publishable keys using unpadded Base64 encoding. Changes include: a new changeset file marking a patch release, updates to \buildPublishableKey()`to strip trailing=` characters from Base64 output and updated JSDoc, and test updates to expect unpadded publishable key strings. No public API signatures were changed.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description check ✅ Passed The PR description includes commit messages and a checklist template related to the changeset, though unchecked items and placeholders are present, it remains related to the core changes of updating key generation and adding a changeset entry.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title directly and clearly summarizes the main change: updating publishable key generation to use unpadded Base64 encoding to match backend output, which is the primary purpose of all three modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@thiskevinwang thiskevinwang changed the title fix(buildPublishableKey): generate unpadded pk fix(shared): generate unpadded pk Apr 25, 2026
@wobsoriano wobsoriano changed the title fix(shared): generate unpadded pk fix(shared): Generated unpadded pk Apr 25, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/shared/src/keys.ts`:
- Line 43: The Base64 padding is being stripped in buildPublishableKey (the line
using isomorphicBtoa(...).replace(/=+$/, '')) which breaks browser atob() used
by isomorphicAtob and thus parsePublishableKey/isPublishableKey; either stop
removing the '=' padding when encoding in buildPublishableKey or, if you must
strip it, update isomorphicAtob/parsePublishableKey to restore padding to a
multiple of 4 before calling atob()/Buffer. Modify the code paths referencing
isomorphicBtoa/isomorphicAtob, buildPublishableKey, parsePublishableKey and
isPublishableKey so decoding always receives properly padded Base64 (restore
padding via '=' until length % 4 === 0) or retain padding on encode.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: f1e3fb79-03e8-415f-8fc0-e9a07d50f131

📥 Commits

Reviewing files that changed from the base of the PR and between 459aa73 and 5d4f79b.

📒 Files selected for processing (2)
  • packages/shared/src/__tests__/keys.spec.ts
  • packages/shared/src/keys.ts

(frontendApi.startsWith('clerk.') && LEGACY_DEV_INSTANCE_SUFFIXES.some(s => frontendApi.endsWith(s)));
const keyPrefix = isDevKey ? PUBLISHABLE_KEY_TEST_PREFIX : PUBLISHABLE_KEY_LIVE_PREFIX;
return `${keyPrefix}${isomorphicBtoa(`${frontendApi}$`)}`;
return `${keyPrefix}${isomorphicBtoa(`${frontendApi}$`).replace(/=+$/, '')}`;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether unpadded base64 is normalized before decode.
# Expected for safety: either
# 1) isomorphicAtob adds '=' padding before decoding, or
# 2) callers in keys.ts pad the encoded segment before passing to isomorphicAtob.

set -euo pipefail

echo "Locate decoder and call sites..."
fd -i 'isomorphicAtob.ts' packages/shared/src
fd -i 'keys.ts' packages/shared/src

ISO_FILE="$(fd -i 'isomorphicAtob.ts' packages/shared/src | head -n1)"
KEYS_FILE="$(fd -i 'keys.ts' packages/shared/src | head -n1)"

echo
echo "---- isomorphicAtob implementation ----"
sed -n '1,220p' "$ISO_FILE"

echo
echo "---- build/decode usage in keys.ts ----"
rg -n -C4 'buildPublishableKey|isomorphicAtob\(|split\('\'_\'\)\[2\]|replace\(/=\+\$/, \x27\x27\)' "$KEYS_FILE"

Repository: clerk/javascript

Length of output: 1057


🏁 Script executed:

#!/bin/bash
# Examine keys.ts to see how unpadded Base64 is handled in decode/parse functions

echo "=== Reading full keys.ts file ==="
cat -n packages/shared/src/keys.ts | head -100

Repository: clerk/javascript

Length of output: 4722


🏁 Script executed:

cat -n packages/shared/src/keys.ts | tail -n +100

Repository: clerk/javascript

Length of output: 8694


🏁 Script executed:

#!/bin/bash
# Search for tests of buildPublishableKey and parsePublishableKey round-trip

find . -type f \( -name '*.test.ts' -o -name '*.test.tsx' -o -name '*.spec.ts' -o -name '*.spec.tsx' \) | xargs rg -l 'buildPublishableKey|parsePublishableKey' 2>/dev/null | head -5

Repository: clerk/javascript

Length of output: 325


🏁 Script executed:

cat packages/shared/src/__tests__/keys.spec.ts

Repository: clerk/javascript

Length of output: 11820


🏁 Script executed:

python3 << 'EOF'
# Check if buildPublishableKey test output strings are valid for browser atob()
# Base64 strings must be multiple of 4 chars when using atob()

test_cases = [
    'ZmFrZS1jbGVyay10ZXN0LmNsZXJrLmFjY291bnRzLmRldiQ',  # fake-clerk-test...dev$
    'Zm9vLWJhci0xMy5jbGVyay5hY2NvdW50cy5kZXYk',        # foo-bar-13...dev$
    'Y2xlcmsuYm9yaW5nLnNhd2ZseS05MS5sY2wuZGV2JA',     # clerk.boring...dev$
    'Y2xlcmsuYm9yaW5nLnNhd2ZseS05MS5sY2xjbGVyay5jb20k', # clerk.boring...com$
]

print("Checking buildPublishableKey test outputs for Base64 validity in atob():\n")
for b64_str in test_cases:
    length = len(b64_str)
    remainder = length % 4
    is_valid_for_atob = remainder == 0
    status = "✓ Valid for atob()" if is_valid_for_atob else f"✗ INVALID ({remainder} chars short of multiple of 4)"
    print(f"Length: {length:2d} ({remainder} % 4 = {remainder}) - {status}")
    print(f"  String: {b64_str}\n")
EOF

Repository: clerk/javascript

Length of output: 583


🏁 Script executed:

python3 << 'EOF'
# Verify what proper Base64 padding should be for these strings
test_cases = [
    ('ZmFrZS1jbGVyay10ZXN0LmNsZXJrLmFjY291bnRzLmRldiQ', 'fake-clerk-test.clerk.accounts.dev$'),
    ('Zm9vLWJhci0xMy5jbGVyay5hY2NvdW50cy5kZXYk', 'foo-bar-13.clerk.accounts.dev$'),
    ('Y2xlcmsuYm9yaW5nLnNhd2ZseS05MS5sY2wuZGV2JA', 'clerk.boring.sawfly-91.lcl.dev$'),
    ('Y2xlcmsuYm9yaW5nLnNhd2ZseS05MS5sY2xjbGVyay5jb20k', 'clerk.boring.sawfly-91.lclclerk.com$'),
]

import base64

print("Verifying Base64 encoding with proper padding:\n")
for unpadded_b64, expected_decoded in test_cases:
    length = len(unpadded_b64)
    remainder = length % 4
    
    # Add proper padding
    padding_needed = (4 - remainder) % 4
    padded_b64 = unpadded_b64 + ('=' * padding_needed)
    
    # Try to decode
    try:
        decoded = base64.b64decode(padded_b64).decode('utf-8')
        match = "✓" if decoded == expected_decoded else "✗"
        print(f"{match} Unpadded: {unpadded_b64}")
        print(f"   Padded:   {padded_b64}")
        print(f"   Decoded:  {decoded}")
        print()
    except Exception as e:
        print(f"✗ Failed to decode: {e}")
        print()
EOF

Repository: clerk/javascript

Length of output: 765


Critical: Unpadded Base64 keys will fail in browser environments.

Line 43 strips all Base64 padding via .replace(/=+$/, ''), but isomorphicAtob passes the unpadded string directly to browser atob(), which requires length to be a multiple of 4. Some keys (e.g., those with decoded length not divisible by 4) will throw in browsers—parsePublishableKey/isPublishableKey will fail for valid keys generated by buildPublishableKey.

Tests pass in Node.js because Buffer.from() accepts unpadded Base64, but browser atob() is stricter. Either restore padding before decode or do not strip padding on encode.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shared/src/keys.ts` at line 43, The Base64 padding is being stripped
in buildPublishableKey (the line using isomorphicBtoa(...).replace(/=+$/, ''))
which breaks browser atob() used by isomorphicAtob and thus
parsePublishableKey/isPublishableKey; either stop removing the '=' padding when
encoding in buildPublishableKey or, if you must strip it, update
isomorphicAtob/parsePublishableKey to restore padding to a multiple of 4 before
calling atob()/Buffer. Modify the code paths referencing
isomorphicBtoa/isomorphicAtob, buildPublishableKey, parsePublishableKey and
isPublishableKey so decoding always receives properly padded Base64 (restore
padding via '=' until length % 4 === 0) or retain padding on encode.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Apr 25, 2026

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@8400

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@8400

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@8400

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8400

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@8400

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8400

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@8400

@clerk/express

npm i https://pkg.pr.new/@clerk/express@8400

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@8400

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8400

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@8400

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@8400

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@8400

@clerk/react

npm i https://pkg.pr.new/@clerk/react@8400

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@8400

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8400

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@8400

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@8400

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8400

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@8400

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@8400

commit: 5d4f79b

@wobsoriano wobsoriano changed the title fix(shared): Generated unpadded pk fix(shared): Generate publishable keys with unpadded Base64 encoding to match backend output Apr 25, 2026
@wobsoriano wobsoriano enabled auto-merge (squash) April 25, 2026 02:30
@wobsoriano wobsoriano merged commit da76490 into main Apr 25, 2026
46 checks passed
@wobsoriano wobsoriano deleted the kevin/fix-buildPublishableKey branch April 25, 2026 02:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants