How to implement Role Based Authorization in T3?

Hey community, I am new to the T3 stack and would like to implement role based authorization in my project. To ensure that only authorized users with the admin role can execute certain queries, I have created a new adminProcedure:
/**
* Admin procedure
*
* Only executes, when the user is authenticated and has the admin role.
*/
export const adminProcedure = t.procedure.use(({ ctx, next }) => {
if (!ctx.session || !ctx.session.user) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}

// Split up for debugging purposes
if (ctx.session.user.role != "ADMIN") {
throw new TRPCError({
code: "UNAUTHORIZED",
message: `No Admin access. Current Role: ${ctx.session.user.role}`,
});
}

return next({
ctx: {
// infers the `session` as non-nullable
session: { ...ctx.session, user: ctx.session.user },
},
});
});
/**
* Admin procedure
*
* Only executes, when the user is authenticated and has the admin role.
*/
export const adminProcedure = t.procedure.use(({ ctx, next }) => {
if (!ctx.session || !ctx.session.user) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}

// Split up for debugging purposes
if (ctx.session.user.role != "ADMIN") {
throw new TRPCError({
code: "UNAUTHORIZED",
message: `No Admin access. Current Role: ${ctx.session.user.role}`,
});
}

return next({
ctx: {
// infers the `session` as non-nullable
session: { ...ctx.session, user: ctx.session.user },
},
});
});
This works in theory, but somehow the roles of the users are not taken over correctly. My sessionCallback looks like this:
export const authOptions: NextAuthOptions = {
callbacks: {
session: ({ session, user }) => ({
...session,
user: {
...session.user,
role: session.user.role,
id: user.id,
},
}),
},
// ...
}
export const authOptions: NextAuthOptions = {
callbacks: {
session: ({ session, user }) => ({
...session,
user: {
...session.user,
role: session.user.role,
id: user.id,
},
}),
},
// ...
}
In my admin procedure, however, the role value always remains undefined. Does anyone have any idea what I am doing wrong or what I am missing? Thanks in advance!
5 Replies
Swiichy
Swiichy5mo ago
export const authOptions: NextAuthOptions = {
callbacks: {
session: ({ session, user }) => ({
...session,
user: {
...session.user,
id: user.id,
role: user.role,
},
}),

redirect: ({ baseUrl }) => {
return Promise.resolve(`${baseUrl}/shop`);
},
},
export const authOptions: NextAuthOptions = {
callbacks: {
session: ({ session, user }) => ({
...session,
user: {
...session.user,
id: user.id,
role: user.role,
},
}),

redirect: ({ baseUrl }) => {
return Promise.resolve(`${baseUrl}/shop`);
},
},
role: session.user.role, => role: user.role,
chris
chris5mo ago
thx for the response, didn't work for me. implemented a fix by making a db request before asigning the role, which works just fine
JulieCezar
JulieCezar5mo ago
I had a similar problem... Although I cant give you a concrete answer, you have to define all the callbacks and pass the user multiple times, or sometimes it doesn't reach the client side like you would expect try going through the docs, writing the code for all callbacks, and console log all parameters so that you can better understand what goes where
Swiichy
Swiichy5mo ago
You use oauth provider ?
JulieCezar
JulieCezar5mo ago
I had like Credentials, Google, Discord and 1 custom one but not oAuth