Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Need some advice for better auth setup#6399

Unanswered
TANISHQBEDI asked this question inQ&A
Discussion options

I am working on a application which requires 2 main roles consultant and client (maybe will have admin for overseeing). I want the user to be able to select roles from client/ consultant when they sign up and go to the onboarding screen. For the first phase i am working on the consultant part, and how it works is that the consultant can manage appointments with the client(book/view/cancel), view all his current clients, view all documents issued to a client. The client will be able to manage appointment with the consultant, view all the different consultants he is going to, view documents issued by those consultants etc. How do i setup better auth for such a app. I have never worked with RBAC before and it is driving me crazy. Any suggestions for such a setup would be appreciated!

You must be logged in to vote

Replies: 1 comment 6 replies

Comment options

Hi@TANISHQBEDI! I'mDosu and I’m helping the better-auth team.

Here’s a practical way to set up RBAC and role selection for your app using better-auth:

  1. Define roles and permissions: Use better-auth’s access control system to create roles like consultant, client, and admin, and map each to the actions they can perform (e.g., consultants can manage appointments, view clients, etc.)—seehow to define roles and permissions.

  2. Role selection at signup: Let users pick their intended role (consultant or client) in your signup UI, but don’t trust user input directly for security. Instead, pass the selected role as context (e.g., in the OAuth2 state parameter or as a custom field in your signup flow), and assign it server-side in auser.create.before hook. This ensures roles are set securely and can’t be spoofed by users. For OTP/email signup, you may need to extend the POST body to include a role field and handle it in your backend logic—seedynamic role assignment example.

  3. Store roles securely: Add arole field to your user schema withinput: false so users can’t set it directly—only your backend logic can. This is important for security—seeadditionalFields config.

  4. Direct users to onboarding: After signup, check the assigned role and route the user to the appropriate onboarding flow in your app.

  5. Enforce permissions: Use better-auth’shasPermission function to check if a user’s role allows them to perform a given action (e.g., booking an appointment, viewing documents). This can be done both server-side and client-side—seepermission checks.

  6. Admin and business roles: Use the admin plugin for app-level admin users, and the organization plugin if you need business-specific roles (like consultant/client) that may belong to different organizations—seeplugin distinction.

Example (simplified):

// permissions.tsimport{createAccessControl}from"better-auth/plugins/access";conststatement={appointment:["book","view","cancel"],client:["list","view"],document:["view","issue"],}asconst;constac=createAccessControl(statement);exportconstconsultant=ac.newRole({appointment:["book","view","cancel"],client:["list","view"],document:["view","issue"],});exportconstclient=ac.newRole({appointment:["book","view","cancel"],consultant:["list","view"],document:["view"],});exportconstadmin=ac.newRole({// all permissions  ...statement,});// auth.tsimport{betterAuth}from"better-auth";import{adminasadminPlugin}from"better-auth/plugins";import{ac,consultant,client,admin}from"./permissions";exportconstauth=betterAuth({user:{additionalFields:{role:{type:"string",input:false},},},plugins:[adminPlugin({      ac,roles:{ consultant, client, admin},defaultRole:"client",// fallback}),],databaseHooks:{"user.create.before":async(ctx)=>{// Assign role based on signup context (e.g., from OAuth2 state or custom field)ctx.data.role=ctx.signupIntentRole||"client";},},});

On the client, after signup, check the user’s role and route to the correct onboarding screen.

If you want more details or a deeper dive into any step, let me know!

To reply, just mention@dosu.


How did I do?Good |Irrelevant |Incorrect |Verbose |Hallucination |Report 🐛 |Other

Chat with Dosu Open in Cursor Join Discord Share on X

You must be logged in to vote
6 replies
@dosubot
Comment options

When you set a field like role to input: false in better-auth, users can't set it directly from the client—this is for security. Here’s how you can still assign roles (or other protected fields) during signup for both email and social providers:

Email/password signup:
Let users pick their role in your signup UI. When you send the signup request, include the role in the request body. Even though input: false blocks direct assignment, you can extract the role in a user.create.before hook on the server and assign it to the user object before creation. Example:

// In your better-auth configdatabaseHooks:{user:{create:{before:async(user,ctx)=>({data:{          ...user,role:ctx.body.role||"client",// assign from request body},}),},},},

Reference

OTP/email signup:
The default OTP plugin only accepts email and otp fields. To pass a role, you need to extend or wrap the plugin to accept a role field in the POST body, then assign it in the user.create.before hook as above. This requires a small patch to the plugin or a custom endpoint.
Reference

Social/OAuth2 providers:
When starting the OAuth2 flow, include the intended role in the state parameter (e.g., state: JSON.stringify({ role: "consultant" })). On the server, extract the role from the state in your user.create.before hook and assign it to the user. This is the recommended way to securely pass contextual data through OAuth flows.

// When starting OAuth2 loginconststate=JSON.stringify({role:"consultant"});// Pass this state to the provider's authorization URL// In your user.create.before hookconst{ role}=ctx.state;// Extracted from OAuth2 state param// Assign to user as above

Reference

If you want the role to be available in the session object on the client, use the customSession plugin to merge user data into the session, since additional fields are not included by default in session responses:

customSession(async({ user, session})=>({  ...session,role:user.role,}))

Reference

Key points:

  • Never trust user input for sensitive fields—always assign them server-side.
  • Use the user.create.before hook to set protected fields based on context.
  • For OAuth2, use the state parameter to pass contextual data.
  • For OTP, you must extend the plugin to accept extra fields.
  • Use customSession if you need these fields in the session on the client.

Let me know if you want a more detailed code example for your specific flow!

To reply, just mention@dosu.


How did I do?Good |Irrelevant |Incorrect |Verbose |Hallucination |Report 🐛 |Other

Chat with Dosu Open in Cursor Join Discord Share on X

@TANISHQBEDI
Comment options

@dosu Thats the issue how do i send the request body with input false?
"use client"
import {Button} from "@/components/ui/button";
import {authClient} from "@/utils/authClient";

export default function Home() {

async function emailSignUp(){    authClient.signUp.email({        email: "test@gmail.com",        password: "password123",        role: "client"    })}async function googleSignUp(){    console.log("Sign Up with Google clicked");}

return (



Sign Up with Email


Sign Up with Google


);
}

Thats what i have so far

@dosubot
Comment options

You can include the role in your signup request body (like you're doing), but because you setinput: false for therole field, better-auth will ignore direct assignment from the client for security. Instead, you must assign the role server-side in auser.create.before hook by extracting it from the request context.

How it works:

  • For email/password signup, send the role in your request body:

    authClient.signUp.email({email:"test@gmail.com",password:"password123",role:"client"// this is just context, not direct assignment})
  • On the backend, use auser.create.before hook to assign the role:

    databaseHooks:{user:{create:{before:async(user,ctx)=>({data:{          ...user,role:ctx.body.role||"client",// securely assign here},}),},},},

    Reference

  • For Google/social signup, pass the role using theadditionalData parameter:

    authClient.signIn.social({provider:"google",additionalData:{role:"consultant"}})

    Then, in your backend hook, extract it usinggetOAuthState:

    import{getOAuthState}from"better-auth/api";databaseHooks:{user:{create:{before:async(user,ctx)=>{conststate=awaitgetOAuthState<{role?:string}>();return{data:{            ...user,role:state?.role||"client",},};},},},},

    Reference

Key points:

  • You can always send the role from the client, but it will only be used if your backend hook picks it up and assigns it.
  • Never trust user input for sensitive fields—always assign them server-side.
  • If you want the role to show up in the session/user object on the client, use theinferAdditionalFields plugin in yourauthClient config or thecustomSession plugin on the backend.

Let me know if you want a full working example for your stack!

To reply, just mention@dosu.


How did I do?Good |Irrelevant |Incorrect |Verbose |Hallucination |Report 🐛 |Other

Chat with Dosu Open in Cursor Join Discord Share on X

@TANISHQBEDI
Comment options

I tried it but i get the error saying
TS2353: Object literal may only specify known properties, and role does not exist in type
Prettify<InferSignUpEmailCtx<{
plugins: ({
id: "additional-fields-client";
$InferServerPlugin: {
id: "additional-fields";
schema: {
user: {
fields: {
role: {
type: "string";
input: false;
};
};
};
session: {
fields: {};
};
};
};
} | { ...; })[];
}, ClientFetchOption<...>>>

@TANISHQBEDI
Comment options

Finally I am going with this kind of approach

"use client"import {Button} from "@/components/ui/button";import {authClient} from "@/utils/authClient";export default function Home() {    const role = "doctor";    async function emailSignUp(){        await authClient.signUp.email({            name: "Test",            email: "test@gmail.com",            password: "password123"        },{            headers:{                "X-Role": role            }        })    }    async function googleSignUp(){        await authClient.signIn.social({            provider: "google",            additionalData:{                role: role            }        })    }  return (    <div>        <Button onClick={emailSignUp}>            Sign Up with Email        </Button>        <Button onClick={googleSignUp}>            Sign Up with Google        </Button>    </div>  );}
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Category
Q&A
Labels
None yet
1 participant
@TANISHQBEDI

[8]ページ先頭

©2009-2025 Movatter.jp