[GH-ISSUE #6081] Bug: hasPermission silently returns false when role doesn't exist in configured roles #19050

Closed
opened 2026-04-15 17:49:55 -05:00 by GiteaMirror · 3 comments
Owner

Originally created by @himself65 on GitHub (Nov 18, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/6081

Bug Report: hasPermission silently returns false when role doesn't exist

Is this suited for GitHub?

  • Yes, this is suited for GitHub

Description

The hasPermission method in the Organization plugin silently returns { error: null, success: false } when a user's role doesn't exist in the configured roles. This makes it impossible to distinguish between:

  1. Role exists but lacks the permission (expected behavior)
  2. Role doesn't exist in the system (should be an error!)
  3. Invalid permission format (should be an error!)

This creates significant debugging challenges for users, as there's no error message, warning, or log to indicate WHY the permission check failed.

To Reproduce

Scenario 1: Role doesn't exist in acRoles

import { organization } from "better-auth/plugins/organization";
import { createAccessControl } from "better-auth/plugins/access";

const ac = createAccessControl({
  project: ["create", "read", "update", "delete"],
});

// Define custom roles, but DON'T include default roles
const customRoles = {
  projectManager: ac.newRole({
    project: ["create", "read", "update"],
  }),
};

const auth = betterAuth({
  plugins: [
    organization({
      roles: customRoles, // owner, admin, member are now missing!
    }),
  ],
});

// User has role "owner" in database, but it's not in customRoles
const result = await client.organization.hasPermission({
  permissions: {
    project: ["create"],
  },
});

console.log(result.data);
// Output: { error: null, success: false }
// Issue: No indication that "owner" role doesn't exist!

Scenario 2: Database has role that doesn't exist in configuration

// User has role "moderator" in the database
// But "moderator" is not defined in the roles configuration

const result = await client.organization.hasPermission({
  permissions: {
    member: ["update"],
  },
});

console.log(result.data);
// Output: { error: null, success: false }
// Issue: Same response as if the role existed but lacked permission!

Current vs. Expected Behavior

Current:

  • Returns { error: null, success: false } when role doesn't exist
  • No logging or error message
  • Impossible to distinguish from legitimate permission denial

Expected:

  • Log a warning when a role doesn't exist: [hasPermission] Role "moderator" not found in configured roles
  • Return an error in the response: { error: "Role 'moderator' not found", success: false }
  • Or throw an error to fail fast during development

Root Cause

The bug is in packages/better-auth/src/plugins/organization/permission.ts:19-26:

for (const role of roles) {
    const _role = acRoles[role as keyof typeof acRoles];  // Could be undefined!
    const result = _role?.authorize(input.permissions ?? input.permission);  // Returns undefined if _role is undefined
    if (result?.success) {
        return true;
    }
}
return false;  // Silent failure - no error!

When _role is undefined (role doesn't exist), the loop continues without any logging or error, eventually returning false with no explanation.

Impact

This bug commonly affects users when:

  1. Custom roles override defaults: Users define custom roles in the plugin config but forget to include default roles (owner, admin, member)
  2. Database role mismatch: A user has a role in the database that doesn't exist in the roles configuration
  3. Typos in role names: "admn" vs "admin"
  4. Migration issues: Roles change during refactoring but database isn't updated

In all cases, users get no feedback about what went wrong, leading to confusion and custom workarounds.

Proposed Fix

Add validation and error handling in permission.ts:

for (const role of roles) {
    const _role = acRoles[role as keyof typeof acRoles];

    if (!_role) {
        // Log warning for debugging
        console.warn(`[hasPermission] Role "${role}" not found in configured roles. Available roles: ${Object.keys(acRoles).join(", ")}`);

        // Option A: Continue to next role (current behavior)
        continue;

        // Option B: Return error immediately (breaking change)
        // return false;

        // Option C: Throw error in development mode
        // if (process.env.NODE_ENV === 'development') {
        //     throw new Error(`Role "${role}" not found in configured roles`);
        // }
    }

    const result = _role.authorize(input.permissions ?? input.permission);
    if (result?.success) {
        return true;
    }
}
return false;

Version

Latest (tested on canary branch, commit 5fa17379c)

Environment

  • OS: macOS (Darwin 25.1.0)
  • Node.js: Latest

Which areas are affected?

  • Backend (Organization plugin)
  • Client (when calling hasPermission)

Additional Context

  • Related to issue #3011 (multiple roles with different permissions) but distinct
  • This affects both static ACL and dynamic ACL configurations
  • Users are currently working around this by writing custom permission checking methods
  • Documentation doesn't clarify that custom roles don't automatically include defaults

Suggested Documentation Improvements

The docs should clarify:

  1. When you define custom roles, you must explicitly include default roles if you want to keep them:

    import { defaultRoles } from "better-auth/plugins/organization";
    
    organization({
      roles: {
        ...defaultRoles,  // Include defaults!
        manager: managerRole,
      }
    })
    
  2. hasPermission returns false for missing roles without error (current behavior)

  3. Best practices for role validation before assignment

Originally created by @himself65 on GitHub (Nov 18, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/6081 ## Bug Report: `hasPermission` silently returns false when role doesn't exist ### Is this suited for GitHub? - [x] Yes, this is suited for GitHub ### Description The `hasPermission` method in the Organization plugin silently returns `{ error: null, success: false }` when a user's role doesn't exist in the configured roles. This makes it impossible to distinguish between: 1. **Role exists but lacks the permission** (expected behavior) 2. **Role doesn't exist in the system** (should be an error!) 3. **Invalid permission format** (should be an error!) This creates significant debugging challenges for users, as there's no error message, warning, or log to indicate WHY the permission check failed. ### To Reproduce **Scenario 1: Role doesn't exist in acRoles** ```typescript import { organization } from "better-auth/plugins/organization"; import { createAccessControl } from "better-auth/plugins/access"; const ac = createAccessControl({ project: ["create", "read", "update", "delete"], }); // Define custom roles, but DON'T include default roles const customRoles = { projectManager: ac.newRole({ project: ["create", "read", "update"], }), }; const auth = betterAuth({ plugins: [ organization({ roles: customRoles, // owner, admin, member are now missing! }), ], }); // User has role "owner" in database, but it's not in customRoles const result = await client.organization.hasPermission({ permissions: { project: ["create"], }, }); console.log(result.data); // Output: { error: null, success: false } // Issue: No indication that "owner" role doesn't exist! ``` **Scenario 2: Database has role that doesn't exist in configuration** ```typescript // User has role "moderator" in the database // But "moderator" is not defined in the roles configuration const result = await client.organization.hasPermission({ permissions: { member: ["update"], }, }); console.log(result.data); // Output: { error: null, success: false } // Issue: Same response as if the role existed but lacked permission! ``` ### Current vs. Expected Behavior **Current:** - Returns `{ error: null, success: false }` when role doesn't exist - No logging or error message - Impossible to distinguish from legitimate permission denial **Expected:** - Log a warning when a role doesn't exist: `[hasPermission] Role "moderator" not found in configured roles` - Return an error in the response: `{ error: "Role 'moderator' not found", success: false }` - Or throw an error to fail fast during development ### Root Cause The bug is in `packages/better-auth/src/plugins/organization/permission.ts:19-26`: ```typescript for (const role of roles) { const _role = acRoles[role as keyof typeof acRoles]; // Could be undefined! const result = _role?.authorize(input.permissions ?? input.permission); // Returns undefined if _role is undefined if (result?.success) { return true; } } return false; // Silent failure - no error! ``` When `_role` is `undefined` (role doesn't exist), the loop continues without any logging or error, eventually returning `false` with no explanation. ### Impact This bug commonly affects users when: 1. **Custom roles override defaults**: Users define custom `roles` in the plugin config but forget to include default roles (owner, admin, member) 2. **Database role mismatch**: A user has a role in the database that doesn't exist in the roles configuration 3. **Typos in role names**: "admn" vs "admin" 4. **Migration issues**: Roles change during refactoring but database isn't updated In all cases, users get no feedback about what went wrong, leading to confusion and custom workarounds. ### Proposed Fix Add validation and error handling in `permission.ts`: ```typescript for (const role of roles) { const _role = acRoles[role as keyof typeof acRoles]; if (!_role) { // Log warning for debugging console.warn(`[hasPermission] Role "${role}" not found in configured roles. Available roles: ${Object.keys(acRoles).join(", ")}`); // Option A: Continue to next role (current behavior) continue; // Option B: Return error immediately (breaking change) // return false; // Option C: Throw error in development mode // if (process.env.NODE_ENV === 'development') { // throw new Error(`Role "${role}" not found in configured roles`); // } } const result = _role.authorize(input.permissions ?? input.permission); if (result?.success) { return true; } } return false; ``` ### Version Latest (tested on canary branch, commit `5fa17379c`) ### Environment - OS: macOS (Darwin 25.1.0) - Node.js: Latest ### Which areas are affected? - Backend (Organization plugin) - Client (when calling hasPermission) ### Additional Context - Related to issue #3011 (multiple roles with different permissions) but distinct - This affects both static ACL and dynamic ACL configurations - Users are currently working around this by writing custom permission checking methods - Documentation doesn't clarify that custom roles don't automatically include defaults ### Suggested Documentation Improvements The docs should clarify: 1. When you define custom `roles`, you must explicitly include default roles if you want to keep them: ```typescript import { defaultRoles } from "better-auth/plugins/organization"; organization({ roles: { ...defaultRoles, // Include defaults! manager: managerRole, } }) ``` 2. `hasPermission` returns `false` for missing roles without error (current behavior) 3. Best practices for role validation before assignment
GiteaMirror added the lockedbug labels 2026-04-15 17:49:56 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Nov 18, 2025):

You’re right about the root cause: the current implementation of hasPermission in the Organization plugin silently returns false when a role isn’t found in the configured roles—there’s no error, warning, or log, so you can’t distinguish between “role missing” and “permission denied” scenarios. This is confirmed in the code at permission.ts, where the function just skips undefined roles and returns false if none match.

Your proposed fix (logging a warning, returning an error, or throwing in development) is feasible and would improve debugging and developer experience. There’s precedent for stricter role validation elsewhere in the codebase (for example, the Admin plugin now throws an error if you try to assign a non-existent role), but the Organization plugin’s permission check doesn’t currently do this cite.

Best practice: always include default roles when customizing, and validate roles before assignment to avoid silent failures. The docs should clarify that custom roles don’t include defaults, and that hasPermission currently returns false for missing roles without error or warning.

If you want to improve this in your project now, you can patch the permission check to log or throw when _role is undefined, as you suggested. This will make debugging much easier and help prevent subtle misconfigurations.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Chat with Dosu Join Discord Share on X

<!-- gh-comment-id:3549654285 --> @dosubot[bot] commented on GitHub (Nov 18, 2025): <!-- Answer --> You’re right about the root cause: the current implementation of `hasPermission` in the Organization plugin silently returns `false` when a role isn’t found in the configured roles—there’s no error, warning, or log, so you can’t distinguish between “role missing” and “permission denied” scenarios. This is confirmed in the code at [`permission.ts`](https://github.com/better-auth/better-auth/blob/a278ecee4efa535ed8ff05d9ce59e33c83d2d3b3/packages/better-auth/src/plugins/organization/permission.ts#L10-L26), where the function just skips undefined roles and returns `false` if none match. Your proposed fix (logging a warning, returning an error, or throwing in development) is feasible and would improve debugging and developer experience. There’s precedent for stricter role validation elsewhere in the codebase (for example, the Admin plugin now throws an error if you try to assign a non-existent role), but the Organization plugin’s permission check doesn’t currently do this [cite](https://github.com/better-auth/better-auth/pull/4842). Best practice: always include default roles when customizing, and validate roles before assignment to avoid silent failures. The docs should clarify that custom roles don’t include defaults, and that `hasPermission` currently returns `false` for missing roles without error or warning. If you want to improve this in your project now, you can patch the permission check to log or throw when `_role` is undefined, as you suggested. This will make debugging much easier and help prevent subtle misconfigurations. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/b0596191-1f8f-4114-acab-db78e579a57c?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/b0596191-1f8f-4114-acab-db78e579a57c?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/b0596191-1f8f-4114-acab-db78e579a57c?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/b0596191-1f8f-4114-acab-db78e579a57c?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/b0596191-1f8f-4114-acab-db78e579a57c?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/b0596191-1f8f-4114-acab-db78e579a57c?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/b0596191-1f8f-4114-acab-db78e579a57c?feedback_type=other)</sup>&nbsp;&nbsp;[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/6081)
Author
Owner

@Ridhim-RR commented on GitHub (Nov 19, 2025):

Working on it

<!-- gh-comment-id:3551312357 --> @Ridhim-RR commented on GitHub (Nov 19, 2025): Working on it
Author
Owner

@dosubot[bot] commented on GitHub (Feb 18, 2026):

Hi, @himself65. I'm Dosu, and I'm helping the better-auth team manage their backlog and am marking this issue as stale.

Issue Summary:

  • You reported that the Organization plugin's hasPermission method returns { error: null, success: false } silently when a user's role is missing.
  • This behavior makes it difficult to distinguish between a missing role and a legitimate permission denial.
  • The maintainer acknowledged this and agreed that adding warnings or errors would improve debugging.
  • Similar stricter validation is already present in the Admin plugin.
  • User Ridhim-RR is currently working on a fix for this issue.

Next Steps:

  • Please let me know if this issue is still relevant with the latest version of better-auth by commenting here.
  • If I don’t hear back within 7 days, I will automatically close this issue.

Thanks for your understanding and contribution!

<!-- gh-comment-id:3921723496 --> @dosubot[bot] commented on GitHub (Feb 18, 2026): Hi, @himself65. I'm [Dosu](https://dosu.dev), and I'm helping the better-auth team manage their backlog and am marking this issue as stale. **Issue Summary:** - You reported that the Organization plugin's `hasPermission` method returns `{ error: null, success: false }` silently when a user's role is missing. - This behavior makes it difficult to distinguish between a missing role and a legitimate permission denial. - The maintainer acknowledged this and agreed that adding warnings or errors would improve debugging. - Similar stricter validation is already present in the Admin plugin. - User Ridhim-RR is currently working on a fix for this issue. **Next Steps:** - Please let me know if this issue is still relevant with the latest version of better-auth by commenting here. - If I don’t hear back within 7 days, I will automatically close this issue. Thanks for your understanding and contribution!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#19050