You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
echo "admin@yourdomain.com" | wrangler secret put ADMIN_EMAIL --env production
echo "your-secure-password" | wrangler secret put ADMIN_PASSWORD --env production
openssl rand -base64 32 | wrangler secret put JWT_SECRET --env production
openssl rand -base64 32 | wrangler secret put BETTER_AUTH_SECRET --env production
But when i try to login, it reports Invalid email or password.
Goal
Login as admin in production.
Effort
I check remote auth_user, auth_account tables,no entries found. Then i use the sql from seed-admin.ts
Now it reports You do not have permission to access this area. I think it's because the missing of rbac record. But seed-admin.ts use rbac service to add user role. I don't know how to deal with it in sql.
const rbac = new RbacService(env.DB)
await rbac.ensureSystemRbacSeed()
await rbac.addUserRoleByName(odid, 'admin')
This is very likely the same root cause as the Better Auth 1.7 float tracked in #1061, with the fix already open in #1062.
packages/core/package.json pins Better Auth with a caret ("better-auth": "^1.6.13"), so a fresh install with no lockfile — a production deploy or a new consumer — resolves to the current latest, 1.7.1:
Better Auth 1.7.0 has breaking schema changes — notably "Accounts now require Account.issuer… Must apply account-identity backfill before deploying" — but 0001_core.sql was written for 1.6.x, and its auth_account table has no issuer column. So on any deploy that floats to 1.7, the account/login/session paths fail at runtime while the rest of the app works — which matches "can't login as admin in production." (Installs that kept a 1.6.x lockfile are unaffected, which is why it's intermittent by environment.)
The fix is #1062 — pin the Better Auth family to an exact, schema-compatible 1.6.25. It's open but hasn't had its CI authorized or a review yet.
We hit this exact sequence on 3.0.0-beta.28 and got all the way through it. Posting the full path because, after the Better Auth 1.7 float in #1061 is pinned, there are two more independent blockers — including the one that left you stuck at You do not have permission to access this area.
Everything below was reproduced with better-auth already pinned to 1.6.31, so none of it is the 1.7 issue.
Blocker 2 — the web registration form creates unusable accounts
The register page posts via htmx to POST /auth/register/form, not to /auth/register. That form endpoint inserts into auth_user but never creates the auth_account row with provider_id = 'credential'. Login reads the password from auth_account, so accounts created through the UI can never log in.
Reproduced side by side on a clean database:
# what the browser actually does
curl -X POST $URL/auth/register/form -b cookies.txt -H "X-CSRF-Token: $CSRF" \
--data-urlencode "firstName=Form" --data-urlencode "lastName=Endpoint" \
--data-urlencode "email=formtest@example.com" --data-urlencode "password=FormTest12345"# -> HTTP 200# the JSON endpoint
curl -X POST $URL/auth/register -b cookies.txt \
-H 'Content-Type: application/json' -H "X-CSRF-Token: $CSRF" \
-d '{"email":"jsontest@example.com","password":"JsonTest12345","firstName":"Json","lastName":"Endpoint"}'# -> HTTP 201
Logging in as the form-created user returns 401 {"error":"Invalid email or password"}, and the worker log says:
WARN [Better Auth]: Credential account not found
auth_user.failed_login_count stays at 0, because the password is never compared — a useful signal for telling this apart from a genuinely wrong password.
Two things make this painful to diagnose:
Registration returns 200, so it looks like it worked.
The account cannot be repaired by hand. auth_user.password_hash stores pbkdf2:100000:<salt>:<hash> while auth_account.password stores Better Auth's scrypt <salt>:<hash>. The formats differ, so you cannot copy the hash between tables — the only recovery is deleting the user and recreating them through the JSON endpoint.
Workaround: create users against POST /auth/register instead of the form.
Blocker 3 — RBAC is unseeded, which is your You do not have permission error
Admin access is not governed by auth_user.role. It is governed by document-backed RBAC. On our fresh install the document types were registered but zero documents existed:
SELECT type_id, count(*) FROM documents WHERE type_id LIKE'rbac%'GROUP BY type_id;
-- 0 rowsSELECT id, is_auth FROM document_types WHERE id LIKE'rbac%';
-- rbac_role | 1-- rbac_verb | 1-- rbac_user_roles | 1
With role = 'admin' and is_super_admin = 1 set on the user, login succeeded and /admin/* still 403'd:
POST /auth/login/form -> Ok
GET /admin/content -> 403
GET /auth/login?error=You%20do%20not%20have%20permission%20to%20access%20this%20area
This looks like the same root cause as #1017 (first-boot FK race leaves RBAC unseeded, and the KV skip-marker latches the broken state). Worth noting we had just created a fresh KV namespace, which matches that description.
How we fixed it. Running the library's own seeding against the database resolves it:
That produces 4 rbac_role, 6 rbac_verb and 1 rbac_user_roles documents. Verified end to end with no other change: /admin/content was 403 before and 200 after.
If you cannot run TypeScript against the remote D1 (getPlatformProxy({remote:true}) needs remote = true on each binding plus an experimental flag, otherwise it silently uses the local database), you can seed by SQL. The linking detail that is easy to miss: the rbac_user_roles document's slug column holds the user id, and its data is {"roleIds":["role-admin"]}. Role and verb documents are referenced by stable slugs (role-admin, verb-read, …), not by their random document ids.
Suggestion
Given how many installs will walk this same path, it might be worth having the deployment docs create the first admin through the JSON endpoint and verify RBAC seeding, until #1017 and #1061 land.
Environment: @sonicjs-cms/core@3.0.0-beta.28, create-sonicjs starter, Cloudflare Workers + D1 (remote production and local miniflare), wrangler 4.131.2, node 22.22.3.
I tried recently in local server and found issue in installed package.
Better Auth rejected the tenant tables in the installed @sonicjs-cms/core package, organizationId was mapped to the SQL column tenant_id instead of the Drizzle field tenantId, and updatedAt was required even though Better Auth never writes it.
Activity
mmcintosh commentedon Aug 21, 2026
This is very likely the same root cause as the Better Auth 1.7 float tracked in #1061, with the fix already open in #1062.
packages/core/package.jsonpins Better Auth with a caret ("better-auth": "^1.6.13"), so a fresh install with no lockfile — a production deploy or a new consumer — resolves to the current latest, 1.7.1:Better Auth 1.7.0 has breaking schema changes — notably "Accounts now require
Account.issuer… Must apply account-identity backfill before deploying" — but0001_core.sqlwas written for 1.6.x, and itsauth_accounttable has noissuercolumn. So on any deploy that floats to 1.7, the account/login/session paths fail at runtime while the rest of the app works — which matches "can't login as admin in production." (Installs that kept a 1.6.x lockfile are unaffected, which is why it's intermittent by environment.)The fix is #1062 — pin the Better Auth family to an exact, schema-compatible
1.6.25. It's open but hasn't had its CI authorized or a review yet.elpastiche commentedon Sep 14, 2026
We hit this exact sequence on 3.0.0-beta.28 and got all the way through it. Posting the full path because, after the Better Auth 1.7 float in #1061 is pinned, there are two more independent blockers — including the one that left you stuck at
You do not have permission to access this area.Everything below was reproduced with
better-authalready pinned to1.6.31, so none of it is the 1.7 issue.Blocker 2 — the web registration form creates unusable accounts
The register page posts via htmx to
POST /auth/register/form, not to/auth/register. That form endpoint inserts intoauth_userbut never creates theauth_accountrow withprovider_id = 'credential'. Login reads the password fromauth_account, so accounts created through the UI can never log in.Reproduced side by side on a clean database:
Resulting rows:
Logging in as the form-created user returns
401 {"error":"Invalid email or password"}, and the worker log says:auth_user.failed_login_countstays at0, because the password is never compared — a useful signal for telling this apart from a genuinely wrong password.Two things make this painful to diagnose:
auth_user.password_hashstorespbkdf2:100000:<salt>:<hash>whileauth_account.passwordstores Better Auth's scrypt<salt>:<hash>. The formats differ, so you cannot copy the hash between tables — the only recovery is deleting the user and recreating them through the JSON endpoint.Workaround: create users against
POST /auth/registerinstead of the form.Blocker 3 — RBAC is unseeded, which is your
You do not have permissionerrorAdmin access is not governed by
auth_user.role. It is governed by document-backed RBAC. On our fresh install the document types were registered but zero documents existed:With
role = 'admin'andis_super_admin = 1set on the user, login succeeded and/admin/*still 403'd:This looks like the same root cause as #1017 (first-boot FK race leaves RBAC unseeded, and the KV skip-marker latches the broken state). Worth noting we had just created a fresh KV namespace, which matches that description.
How we fixed it. Running the library's own seeding against the database resolves it:
That produces 4
rbac_role, 6rbac_verband 1rbac_user_rolesdocuments. Verified end to end with no other change:/admin/contentwas 403 before and 200 after.If you cannot run TypeScript against the remote D1 (
getPlatformProxy({remote:true})needsremote = trueon each binding plus an experimental flag, otherwise it silently uses the local database), you can seed by SQL. The linking detail that is easy to miss: therbac_user_rolesdocument'sslugcolumn holds the user id, and itsdatais{"roleIds":["role-admin"]}. Role and verb documents are referenced by stable slugs (role-admin,verb-read, …), not by their random document ids.Suggestion
Given how many installs will walk this same path, it might be worth having the deployment docs create the first admin through the JSON endpoint and verify RBAC seeding, until #1017 and #1061 land.
Environment:
@sonicjs-cms/core@3.0.0-beta.28,create-sonicjsstarter, Cloudflare Workers + D1 (remote production and local miniflare), wrangler 4.131.2, node 22.22.3.ruledweb commentedon Oct 10, 2026
I tried recently in local server and found issue in installed package.
Better Auth rejected the tenant tables in the installed @sonicjs-cms/core package, organizationId was mapped to the SQL column tenant_id instead of the Drizzle field tenantId, and updatedAt was required even though Better Auth never writes it.