TRPC Security/ Authorization Concerns
Hey guys, I'm a bit worried the default protected procedure isn't enough to secure data in my app. As I understand it, if someone just logs in my app, he'll have a valid session, and could potentially reverse engineer the trpc queries and access other users data, is there something I'm missing here ?
I'm also incredibly confused by TRPC context, should I be querying my db in the context to check if user has authorization for the data he's requesting ? Every example I find is only passing session and db connection, haven't seen any type of authorization method here.
11 Replies
T3 stacks provides authentication functionality for TRPC, i.e.
protectedProcedure
but does not handle authorization as that is a complex problem to generically solve. The simplest thing you could do is check if the user owns that particular resource by using DB queries. There are also some libraries like https://github.com/stalniy/casl which provide a much more thorough layer of authorizationGitHub
GitHub - stalniy/casl: CASL is an isomorphic authorization JavaScri...
CASL is an isomorphic authorization JavaScript library which restricts what resources a given user is allowed to access - GitHub - stalniy/casl: CASL is an isomorphic authorization JavaScript libra...
I think you'll have to write your own code to give users permissions in your db. You can probably write some more code in your middleware file to check requests against the roles you've given the users in your db in order to allow or disallow them. You can probably insert the role they have into the context you pass along in your middleware and then return the proper TRPC error code on your router functions
Thanks for the suggestion, unfortunately I'm using drizzle and casl doesn't seem to have a package for drizzle, same with zenstack. Is there a good alternative for drizzle ?
But also, my authorization rules will not be too complex, I was thinking of creating a table for authorizations and querying my db in the context, but not sure if this is a good thing to do
This is fine to do depending on how you do it. I've found the easiest primitive way is to just have an ownerId column on DB resources and check if the session user id is equal to the ownerId
If someone has a session yes they will have access to protected procedures, but if the procedure does something like
getUserById({userId: session.userId})
then they will only get their own info back.
Another example is something like deletePostById, you dont just do deletePostById({id: input.id})
you do deletePostById({id:input.id, creatorId: session.userId})
and hence they will only be able to delete their own posts
These are just examples, but you are correct, protectedProcedure
is just "Authentication". you still have to think about who has access to what aka "Authorization" yourself
A common thing is managing authorization with user roles but you could choose to make it as specific as your usecase needs to beThat was a very good explanation. Made me realize that what I need to do, is add more data to my session object, and use that value on the calls, instead of just receiving as an input
Cody is in a very similar situation to mine in this video https://youtu.be/I5UXsAW9dUE?si=mGjih6SBAtJT3FNG
I don't really like the way he solves this since we could always forget to add that assertion. Given this example from Cody, would it make sense to have the classroomId in the session, and change it based on the classroom you're currently managing so we could then access the classroomId in the trpc procedures ?
Web Dev Cody
YouTube
How I implement role based authorization to my T3 Stack application
š¬ Discord https://discord.gg/4kGbBaa
š¤ Patreon https://www.patreon.com/webdevjunkie
š Newsletter http://eepurl.com/hnderP
š. GitHub https://github.com/codyseibert/youtube
My VSCode Extensions:
- theme: material community high contrast
- fonts: Menlo, Monaco, 'Courier New', monospace
- errors: Error Lens
- extra git help: Git Lens
- tailwind ...
I mean in programming there are always many way to do things, I feel like you are asking for "best practise" or something but as I said before, you just have to pick a way to do it that fits your use case.
That being said, no I personally would not put classroomId in session.
If your regular
protectedProcedure
looks like this
you could add another
now we can use teacherProcedure
instead and again we are back to the situation where you do something like deleteClassroom({id:input.id, teacherId: session.userId})
This was incredibly helpful, thank you so much @andersgee
Inspired by this, I came up with the solution I think fits this best.
Still based on cody's project so others can understand it better here's what I ended up with:
This part was what unlocked my brain mostly, I didn't know that I could easily get the input from the request here (finally read trpc documentation lol).
This works perfectly for my case, since I was already sending the classroomId as input to all the procedures I needed, just like Cody is doing on the video.
Note: Be careful with the input, rawInput means that it hasn't been validated with zod yet, so we validate here.
youre welcome š
btw about "careful with input", the typesafe way is also a 1 liner so might aswell use that
I'll edit it above, thank you!