[PR #1538] [CLOSED] feat(organization): Add dynamic role authorization #3857

Closed
opened 2026-03-13 11:17:50 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/better-auth/better-auth/pull/1538
Author: @unitythemaker
Created: 2/22/2025
Status: Closed

Base: mainHead: main


📝 Commits (7)

  • 501cb2c feat(organization): add custom role resolution function to organization plugin
  • 7a790b3 feat: add utils for organization, temporarily change packageName
  • db29bd4 feat(organization): allow custom role authorization logic and remove utility back
  • 0d03f33 fix: revert package.json back to normal
  • 0f0db1b lint: fix
  • b213d43 test: add await to modified function calls
  • cecb424 fix: revert unintentionally updated code

📊 Changes

9 files changed (+95 additions, -33 deletions)

View changed files

📝 packages/better-auth/package.json (+1 -1)
📝 packages/better-auth/src/plugins/organization/access/access.ts (+7 -2)
📝 packages/better-auth/src/plugins/organization/call.ts (+1 -0)
📝 packages/better-auth/src/plugins/organization/client.ts (+5 -2)
📝 packages/better-auth/src/plugins/organization/organization.test.ts (+3 -3)
📝 packages/better-auth/src/plugins/organization/organization.ts (+15 -2)
📝 packages/better-auth/src/plugins/organization/routes/crud-invites.ts (+24 -7)
📝 packages/better-auth/src/plugins/organization/routes/crud-members.ts (+21 -8)
📝 packages/better-auth/src/plugins/organization/routes/crud-org.ts (+18 -8)

📄 Description

This PR is part of a stack, managed by mrge:


Problem

Currently, implementing custom role authorization (e.g. database-backed roles) requires creating full role objects with statements and complex authorization logic. This makes it difficult to integrate with existing permission systems or databases.

More info

If I needed to use my custom role, I couldn't. Because, I would needed to do the following code. In the code, roles must be defined ahead-of-time before the betterAuth is initialized. This requires me to make a call to database before initializing better auth, generate roles and give them to better auth. This also limits me to modify permissions, add/remove roles after the application starts running.
At the same time; role.authorize function is being called synchronously (without await). Because of this, I cannot query the database or do some custom authorization logic and/or use my own custom permission/access control system.

import { betterAuth } from "better-auth"
import { organization } from "better-auth/plugins"
import { createAccessControl } from "better-auth/plugins/access";
 
const statement = { 
    project: ["create", "share", "update", "delete"],
} as const; 
 
const ac = createAccessControl(statement); 
 
const member = ac.newRole({ 
    project: ["create"], 
}); 
 
const admin = ac.newRole({ 
    project: ["create", "update"], 
}); 
 
const owner = ac.newRole({ 
    project: ["create", "update", "delete"], 
});
 
export const auth = betterAuth({
    plugins: [
        organization({
            ac: ac,
            roles: {
                owner,
                admin,
                member,
                myCustomRole
            }
        }),
    ],
});

With database example:

import { betterAuth } from "better-auth"
import { organization } from "better-auth/plugins"
import { createAccessControl } from "better-auth/plugins/access";
 
const statement = { 
    project: ["create", "share", "update", "delete"],
} as const; 
 
const ac = createAccessControl(statement); 
 
// Define statically, or query database and create these roles dynamically.
// Example of what you'd need to do:
const roles = await db.getRoles();
const roleObjects = {};

for (const role of roles) {
    roleObjects[role.name] = ac.newRole({
        project: role.permissions.project || [],
        // You'd need to map all your database permissions
        // to this static format for each role
        organization: role.permissions.organization || [],
        member: role.permissions.member || [],
        invitation: role.permissions.invitation || [],
    });
}
 
export const auth = betterAuth({
    plugins: [
        organization({
            ac: ac,
            roles: roleObjects
        }),
    ],
});

Solution

Added an authorize function option that simplifies custom role authorization:

organization({
    creatorRole: 'some-owner-role-id',
    authorize: async (request, roleId) => {
        const permissions = await someDb.getRolePermissions(roleId);
        return {
            success: permissions.includes(request.permission),
            error: "Not authorized"
        };
    }
})

@Bekacru


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/better-auth/better-auth/pull/1538 **Author:** [@unitythemaker](https://github.com/unitythemaker) **Created:** 2/22/2025 **Status:** ❌ Closed **Base:** `main` ← **Head:** `main` --- ### 📝 Commits (7) - [`501cb2c`](https://github.com/better-auth/better-auth/commit/501cb2c97897fc28c984397106843fdeedafc4d3) feat(organization): add custom role resolution function to organization plugin - [`7a790b3`](https://github.com/better-auth/better-auth/commit/7a790b33a364248ee8c0e06426e865f38dfe9485) feat: add utils for organization, temporarily change packageName - [`db29bd4`](https://github.com/better-auth/better-auth/commit/db29bd430c4d9fa0fc29a753d5bb1231d137715b) feat(organization): allow custom role authorization logic and remove utility back - [`0d03f33`](https://github.com/better-auth/better-auth/commit/0d03f332085be765919394e8dfa691a5417081b0) fix: revert package.json back to normal - [`0f0db1b`](https://github.com/better-auth/better-auth/commit/0f0db1bfc5cde737d13d9feaec7bf77627015ffd) lint: fix - [`b213d43`](https://github.com/better-auth/better-auth/commit/b213d43f88b6cb6591bb1e0d1297fc99193ee49a) test: add await to modified function calls - [`cecb424`](https://github.com/better-auth/better-auth/commit/cecb424bcff5ae5777dd51b7ba6b940bfc535302) fix: revert unintentionally updated code ### 📊 Changes **9 files changed** (+95 additions, -33 deletions) <details> <summary>View changed files</summary> 📝 `packages/better-auth/package.json` (+1 -1) 📝 `packages/better-auth/src/plugins/organization/access/access.ts` (+7 -2) 📝 `packages/better-auth/src/plugins/organization/call.ts` (+1 -0) 📝 `packages/better-auth/src/plugins/organization/client.ts` (+5 -2) 📝 `packages/better-auth/src/plugins/organization/organization.test.ts` (+3 -3) 📝 `packages/better-auth/src/plugins/organization/organization.ts` (+15 -2) 📝 `packages/better-auth/src/plugins/organization/routes/crud-invites.ts` (+24 -7) 📝 `packages/better-auth/src/plugins/organization/routes/crud-members.ts` (+21 -8) 📝 `packages/better-auth/src/plugins/organization/routes/crud-org.ts` (+18 -8) </details> ### 📄 Description <!-- MRGE_STACK_DESCRIPTION_START --> This PR is part of a [stack](https://docs.mrge.io/overview), managed by [mrge](https://mrge.io): * `main` (default branch) * **[#1538: feat(organization): Add dynamic role authorization](https://github.com/better-auth/better-auth/pull/1538)** ⬅️ Current PR ([View on mrge](https://mrge.io/pr/better-auth/better-auth/pull/1538)) * [#2209: docs(hono): add client-side auth (hono RPC) configuration guide](https://github.com/better-auth/better-auth/pull/2209) --- <!-- MRGE_STACK_DESCRIPTION_END --> ## Problem Currently, implementing custom role authorization (e.g. database-backed roles) requires creating full role objects with statements and complex authorization logic. This makes it difficult to integrate with existing permission systems or databases. <details> <summary>More info</summary> If I needed to use my custom role, I couldn't. Because, I would needed to do the following code. In the code, roles must be defined ahead-of-time before the betterAuth is initialized. This requires me to make a call to database before initializing better auth, generate roles and give them to better auth. This also limits me to modify permissions, add/remove roles after the application starts running. At the same time; ``role.authorize`` function is being called synchronously (without ``await``). Because of this, I cannot query the database or do some custom authorization logic and/or use my own custom permission/access control system. ```typescript import { betterAuth } from "better-auth" import { organization } from "better-auth/plugins" import { createAccessControl } from "better-auth/plugins/access"; const statement = { project: ["create", "share", "update", "delete"], } as const; const ac = createAccessControl(statement); const member = ac.newRole({ project: ["create"], }); const admin = ac.newRole({ project: ["create", "update"], }); const owner = ac.newRole({ project: ["create", "update", "delete"], }); export const auth = betterAuth({ plugins: [ organization({ ac: ac, roles: { owner, admin, member, myCustomRole } }), ], }); ``` With database example: ```typescript import { betterAuth } from "better-auth" import { organization } from "better-auth/plugins" import { createAccessControl } from "better-auth/plugins/access"; const statement = { project: ["create", "share", "update", "delete"], } as const; const ac = createAccessControl(statement); // Define statically, or query database and create these roles dynamically. // Example of what you'd need to do: const roles = await db.getRoles(); const roleObjects = {}; for (const role of roles) { roleObjects[role.name] = ac.newRole({ project: role.permissions.project || [], // You'd need to map all your database permissions // to this static format for each role organization: role.permissions.organization || [], member: role.permissions.member || [], invitation: role.permissions.invitation || [], }); } export const auth = betterAuth({ plugins: [ organization({ ac: ac, roles: roleObjects }), ], }); ``` </details> ## Solution Added an `authorize` function option that simplifies custom role authorization: ```typescript organization({ creatorRole: 'some-owner-role-id', authorize: async (request, roleId) => { const permissions = await someDb.getRolePermissions(roleId); return { success: permissions.includes(request.permission), error: "Not authorized" }; } }) ``` @Bekacru --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
GiteaMirror added the pull-request label 2026-03-13 11:17:50 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#3857