AccueilClients

Applications et sites

  • Application métiersIntranet, back-office...
  • Applications mobilesAndroid & iOS
  • Sites InternetSites marketings et vitrines
  • Expertises techniques

  • React
  • Expo / React Native
  • Next.js
  • Node.js
  • Directus
  • TypeScript
  • Open SourceBlogContactEstimer

    15 octobre 2025

    Better Auth : structure et permissions avec le plugin Organization

    9 minutes de lecture

    Better Auth : structure et permissions avec le plugin Organization
    🇺🇸 This post is also available in english

    Après avoir exploré les bases de Better Auth, nous allons aujourd'hui nous intéresser à l’un des plugins que nous avions mentionné : Organization.

    Gérer une hiérarchie d’utilisateurs et de permissions dans une application peut vite devenir un casse-tête. Le plugin Organization de Better Auth propose une approche élégante et modulaire : il permet de gérer des structures multi-utilisateurs (équipes, entreprises, projets...), d’y associer des rôles et d’implémenter des permissions.

    Voici la liste des points que nous allons aborder :

    • la gestion du state entre client et serveur,
    • la personnalisation du modèle Organization,
    • la mise en place d’un système de permissions avancé,
    • et enfin l’activation du mode Teams, pour les cas les plus complexes.

    Séparation du state entre serveur et client

    Un point de base essentiel à comprendre est que auth (serveur) et authClient (client) fonctionnent comme deux systèmes séparés, ils ne partagent pas d’état synchronisé automatiquement.

    👉 En pratique :

    • Les actions exécutées via auth.api ne mettent pas à jour automatiquement les hooks client (ex. useSession, useActiveOrganization).
    • Il faut rafraîchir explicitement l’état client après toute action ayant un impact sur la session, l’organisation ou les rôles. Par exemple, le hook useActiveOrganization() propose un refetch() pour rafraîchir les données de l’organisation active côté client.

    Ce comportement découle de la séparation entre client et serveur : Better Auth ne synchronise pas automatiquement ces deux états. Cela donne au développeur un contrôle total sur le rafraîchissement côté client. Similaire à d’autres librairies d’authentification modernes, la synchronisation automatique du state n’est généralement pas implémentée pour des raisons de performance et de sécurité.

    Personnaliser le modèle Organization

    Les tables nécessaires au plugin Organization sont personnalisables : il est possible de les renommer ainsi que leurs champs ou d'ajouter des champs supplémentaires. Le CLI de Better Auth se chargera ensuite de générer le schéma correspondant dans votre base de données via la commande npx @better-auth/cli generate (ici, nous utilisons Prisma avec son adapter).

    Dans cet exemple, nous allons renommer Organization en Project et son champ name en title (mais dans le reste de l'article, nous continuerons à parler de organization).

    plugins: [
      organization({
        schema: {
          organization: {
            modelName: 'project', // `Organization` sera renommé `Project`
            fields: {
              name: 'title', // `name` sera renommé `title`
            },
          },
          member: {
            additionalFields: {
              name: {
                // On ajoute un champ `name` à la table `Member`
                type: 'string',
                input: true,
                required: false,
              },
            },
          },
        },
      }),
    ]
    

    ⚠️ Le renommage n’affecte pas les noms des méthodes SDK (authClient.organization.*, auth.api.*).

    // ✅ Correct
    const { data, error } = await authClient.organization.delete({
      organizationId: activeOrganization!.id,
    })
    
    // ❌ Incorrect
    const { data, error } = await authClient.project.delete({
      projectId: activeProject!.id,
    })
    

    Mettre en place un système de permissions

    Gestion des rôles : capture de l’UI des permissions

    C’est sans doute l'une des parties les plus intéressantes du plugin Organization : Better Auth fournit un système d’Access Control flexible avec son createAccessControl. Il s’appuie sur une approche déclarative de la sécurité, où chaque rôle se voit attribuer un ensemble d’actions autorisées sur des entités précises.

    Résumé des termes :

    • Entité → représente un objet sur lequel on agit (organization, member, invitation).
    • Action → représente ce qu’on peut faire sur une entité (update, delete, etc.).
    • Permission → est la combinaison entité + action (un member avec l'action create sur l'entité invitation peut créer une invitation).
    • Rôle → est un ensemble de permissions accordées à un utilisateur.

    Exemple de fichier de configuration :

    Voici un exemple minimal pour déclarer des actions par entité et composer des rôles.

    import { createAccessControl } from 'better-auth/plugins/access'
    
    const statement = {
      organization: ['update', 'delete'],
      member: ['create', 'update', 'delete', 'update-name'],
      invitation: ['create', 'cancel'],
    } as const
    
    const ac = createAccessControl(statement)
    
    const member = ac.newRole({
      member: ['update-name'],
    })
    
    const admin = ac.newRole({
      member: ['update', 'delete', 'update-name'],
      invitation: ['create', 'cancel'],
    })
    
    const owner = ac.newRole({
      organization: ['update', 'delete'],
      member: ['update', 'delete', 'update-name'],
      invitation: ['create', 'cancel'],
    })
    
    export { statement, ac, owner, admin, member }
    

    Points importants

    • Un utilisateur peut avoir plusieurs rôles à la fois.
    • Vous pouvez définir vos propres permissions (ex. update-name).
    • Si vous définissez un ac et des roles personnalisés, les permissions par défaut de Better Auth sont écrasées. Better Auth s’attend à retrouver certaines actions pour ses checks internes ; il faut donc réintroduire les actions de base si vous souhaitez utiliser les méthodes natives du plugin :
      • organization: ["update", "delete"]
      • member: ["create", "update", "delete"]
      • invitation: ["create", "cancel"]

    Si une de ces actions est absente, les rôles qui ne la possèdent pas ne pourront pas exécuter les méthodes correspondantes (auth.organization.update, auth.organization.inviteMember, etc.) : elles seront considérées comme non autorisées.

    Permission personnalisée

    Vous vous demandez peut-être pourquoi nous avons à la fois update et update-name dans nos permissions. Intuitivement, on pourrait penser que update couvre toutes les actions de mise à jour mais dans le plugin Organization, update fait uniquement référence à la méthode updateMemberRole, c'est-à-dire au changement de rôle d'un membre.

    Cela s'explique par le fait que la table Member ne contient aucun champ que l'on souhaiterait modifier par défaut autre que role. Notre permission personnalisée update-name n'aura donc de sens que si nous ajoutons un champ name à la table Member et que nous implémentons la logique métier correspondante (via nos propres routes ou actions serveur).

    Vérifier les permissions côté client et côté serveur

    Une bonne pratique consiste à effectuer un double check :

    • Côté client → pour la logique d’interface (afficher/masquer un bouton)
    • Côté serveur → pour la sécurité et la source de vérité

    Côté client

    authClient.organization.checkRolePermission({
      permissions: { organization: ['update'] },
      role: 'owner',
    })
    

    Cette méthode est synchrone et ne dépend pas du serveur. Elle est idéale pour ajuster l’UI selon les permissions du rôle mais ne prend pas en compte les éventuelles mises à jour récentes (changement de rôle, révocation, etc.).

    Côté serveur

    Le serveur reste la source de vérité.
    Les rôles et permissions étant stockés en base, il faut toujours valider côté serveur :

    await auth.api.hasPermission({
      headers: await headers(),
      body: {
        permissions: { organization: ['update'] },
      },
    })
    

    Voici un petit utilitaire ainsi que notre implémentation côtés client/serveur :

    import { statement } from '@/lib/auth/permissions'
    
    export type Entities = keyof typeof statement
    export type PermissionFor<E extends Entities> = (typeof statement)[E][number]
    

    Gérer les invitations

    Better Auth fournit un système intégré de gestion des invitations qui permet d’ajouter de nouveaux membres à une organisation.

    Invitations : capture de l’interface

    Les méthodes SDK

    Côté client (authClient) comme côté serveur (auth), le fonctionnement est identique :

    // Côté client
    // ⚠️ Pensez à rafraîchir l’état côté client si nécessaire
    
    const { data, error } = await authClient.organization.listInvitations({
      query: {
      organizationId: "organization-id",
      },
    });
    
    // Côté serveur, la méthode équivalente à inviteMember s’appelle `createInvitation`
    const { data, error } = await authClient.organization.inviteMember({
      email: "example@gmail.com",
      role: "member",
      organizationId: "org-id",
      resend: true,
      teamId: "team-id",
    });
    
    const { data, error } = await authClient.organization.acceptInvitation({
      invitationId: "invitation-id",
    });
    
    await authClient.organization.rejectInvitation({
      invitationId: "invitation-id",
    });
    
    await authClient.organization.cancelInvitation({
      invitationId: "invitation-id",
    });
    
    

    Callbacks

    Après la création d'une invitation avec inviteMember (authClient) ou createInvitation (auth), Better Auth exécute automatiquement le callback sendInvitationEmail qui permet d’envoyer un e-mail personnalisé contenant l’invitationId.

    Il existe également un callback onInvitationAccepted déclenché lorsque l'utilisateur accepte une invitation :

    import { betterAuth } from 'better-auth'
    import { organization } from 'better-auth/plugins'
    
    export const auth = betterAuth({
      plugins: [
        organization({
          async sendInvitationEmail(data) {
            // Gestion de votre envoi d'invitation par email
          },
          async onInvitationAccepted(data) {
            // Gestion après qu'un utilisateur accepte une invitation
          },
        }),
      ],
    })
    

    ⚙️ De notre côté, dans le mail d'invitation, nous avons inclus un lien vers une page de redirection /accept-invitation/[invitationId]. Elle redirige l'utilisateur vers la page de connexion s’il n’est pas authentifié ou vers une page de gestion dédiée où il peut accepter ou refuser son invitation.

    Pour aller plus loin : le mode Teams

    Better Auth permet d’ajouter un niveau hiérarchique supplémentaire grâce au flag de configuration teams. Chaque organisation pourra ainsi avoir différentes équipes, chacune avec ses propres membres et rôles.

    C’est particulièrement utile pour les applications complexes où :

    • une entreprise regroupe plusieurs départements,
    • un utilisateur a des rôles différents selon l’équipe,
    • on souhaite affiner les permissions sans multiplier les organisations.
    import { betterAuth } from 'better-auth'
    import { organization } from 'better-auth/plugins'
    
    export const auth = betterAuth({
      plugins: [
        organization({
          teams: { enabled: true },
        }),
      ],
    })
    

    Better Auth gère automatiquement les relations entre organization, team et member : les utilisateurs pourront faire partie de plusieurs équipes au sein d'une même organisation. Le système d’Access Control s’applique également au niveau de l’équipe, permettant par exemple de limiter une action à un rôle dans une seule équipe.

    Conclusion

    Le plugin Organization de Better Auth offre une flexibilité impressionnante, il permet de construire un système d’accès robuste et lisible. Le système de permissions est entièrement extensible, vous pouvez créer vos propres entités (project, team, workspace) et définir autant de types d’actions que nécessaire pour coller à vos besoins métier !

    Avec la configuration Teams, on peut modéliser des structures hiérarchiques avancées sans perdre la simplicité de son API.

    Better Auth ne se limite donc pas à l’authentification, c’est un véritable socle d’autorisation moderne, extensible et agréable à utiliser.

    À découvrir également

    Premier Octet vous accompagne dans le développement de vos projets avec nextjs

    Discuter de votre projet nextjs