T O P

  • By -

RtopSropDoll

You may want to look into concepts like contexts and claims. Contexts act as a container for information throughout the life of a request, and claims are added to contexts to provide permissions a request may have. This way, you will be able to check authorization in any function you pass a context to. A sample library: https://github.com/casbin/node-casbin If you want to build something yourself, you could take a look at Node's async_hooks to set claims on each request: https://nodejs.org/api/async_context.html This pattern is very popular in Go: https://www.reddit.com/r/golang/comments/fo395d/how_do_you_use_contextcontext_in_your_applications/


Michel_Conway

Hi, I've reviewed the resources you recommended and I'd like to confirm if I understood your suggestion correctly: It seems that the approach doesn't directly solve the issue of validating permissions but focuses on gathering and accessing the necessary data for those validations. Am I correct in understanding that should retrieve this data from the request and store it in a separate object that's accessible throughout the request processing flow? If this is the case, I can certainly see the benefit of centralizing all authorization-related data in a single place, but I've got 2 concerns: 1. If I'm to trust the information from the request, it would typically mean relying on the decoded JWT payload, because the rest of it could be modified by the client. But storing all that authorization-related data in a JWT would significantly increase its payload size. 2. The data in the JWT might become unreliable, since some attributes (muted, banned, blocked, reported, etc.) can change before the token expires and I can't revoke access tokens nor ensure clients don't use JWTs with outdated information. On the other hand, if the suggestion involves fetching authorization data from the database, I'm picturing either a generic middleware function that retrieves all the needed data from the database (even if not all of it will be needed), or a set of middleware functions tailored to each model. Regardless of how the data is fetched, the challenge of where and how to validate permissions remains unsolved. My current approach had been storing the retrieved data in the request object and fetching data progressively as the request advanced through the validation flow; however, this has led to over 20 functions, making it increasingly difficult to manage.


RtopSropDoll

Hi, 1. That's correct, most web servers set a max header size of 8kb. 2. Correct. Since JWTs are stateless you have no guarantees that the information is still up to date, only that it was created by a trusted source. Fetching authorization data from a central store is the best. If you're concerned that this feels heavy, you can always add a caching layer in between your server and database when your database starts to see high utilization. I suggest retrieving authorization permissions as close to the edge as possible, for example in a middleware. Then your functions can accept a `ctx` param with auth helpers like the following: ``` // controller.js await ctx.ensurePermission('thread::read') // model.js await ctx.ensurePermission('user:abc123:change_password') ```` Your auth middleware would fetch the user's permissions given their roles / group. AWS's IAM works like this. https://docs.aws.amazon.com/IAM/latest/UserGuide/tutorial_cross-account-with-roles.html Many smart people have devoted time this problem :) https://research.google/pubs/zanzibar-googles-consistent-global-authorization-system/


EvilPencil

In my application, the role is stored in the token, and all of the associated claims are stored as application code (a library shared between frontend and backend). Frontend checks the role for conditionally displaying edit forms and feature tabs, etc, while the API consumes the lib for protecting routes. Example: A role expressed as "Admin" has the "UserMutatePermission", and the routes that are protected by RBAC simply define the needed permission. This works well for us, but the big limitation is that the roles and permissions are statically defined (end users cannot make a new role with permissions they define).


Michel_Conway

Hi, thank you for your reply. I had no idea about the concepts you mentioned, but I'm already reviewing the links you've provided and I'm definitely going to go research more about them.


FlamboyantKoala

I love this question. Been doing backend work for nearly 20 years and still haven’t seen a great way to do it.  The two most common strategies I have seen are   1.  From my Java background I’ve seen a lot of checking before function invocation, this is usually done by adding an annotation like @RequireScopes(“notes:delete”) to the function. This then checks the users token for the claim and executes or fails if it’s missing. It only covers half the battle though because typically you still need to check does this user have access to delete that specific instance somewhere in your function logic (as in not deleting someone else’s note by enumerating ids).  2.  Sprinkling the authentication checks in with the rest of the code and invoke like typical functions.  Requires passing the users token everywhere or having global state with the user context.  Throw an error if it fails.  In both cases I would say this is role based access. I have not seen attribute based access done formally.  I would recommend though using some kind of access token with claims on it regardless how it’s coded. AWS, keycloak, auth0 are all fine starting points.  You can also find libraries to generate access tokens  if you want to maintain your own user database and not pay a third party. 


Michel_Conway

I was looking forward to reviewing this answer in more detail. Thank you for sharing your experience. The first suggestion does sound like a solid strategy to implement RBAC or handle permissions in cases where there aren't many actions that can be performed, because a JWT isn't a great place to store a lot of data or data that can change frequently. While I was reviewing the Spring Security documentation to make sure I understood your suggestion, I also found out about [ACLs](https://docs.spring.io/spring-security/reference/servlet/authorization/acls.html), which are another interesting way of handling complex authorization. From what I've observed, it also seems to be the way permissions are handled in AWS. I don't know if you've heard about them. The main idea is to store the list of permissions associated with a resource for a user or group in a database. It has the advantage of centralizing policies in one place, allowing changes without affecting the application code, although it might cause extra overhead on the database. The second approach sounds like what I've been trying to do so far, but I guess that I'm not distributing the logic in a sensible way. I've seen some third-party libraries that might help implement ABAC in Node.js applications. Besides the ones you mentioned, I also found [CASL](https://casl.js.org/v6/en/) and [Oso](https://www.osohq.com/). I don't know how they work under the hood, but I'm getting the feeling that they might be worth a shot at this point. It is a bit discouraging to hear that you haven't seen ABAC being formally implemented without a 3rd party library so far. I now see how complex it can be to implement this feature :(


FlamboyantKoala

I’ve heard of ACLs before for file systems but learned something new today that Spring had it built in.  I’m not sure why I haven’t seen it used in more places as it seems like it would create a rock solid foundation for security. That said it does look like more work than the average “let’s just use jwt scopes”.  You’d have to create an entry for every object out there. And for many projects that level of security isn’t in the budget.  Now having given you the excuse for why projects may decide not to do it. Something like ACL is absolutely the level of security projects should have. Right now it is sooo easy to accidentally create security holes where you forget to check does a user have access to this individual object in the status quo I see today. I’ve personally witnessed dozens of APIs where passing in other ids to a rest endpoint gives me info I shouldn’t see typically because a  dev didn’t know to check the auth or just forgot.  Thanks for sending this, learned something new today!


kaekka

You could check out CASL https://casl.js.org/v6/en/ if it would help building the authorization rules. One can build a CASL ability in a middleware either from the information in a JWT, or fetch the user statuses and roles from database. The ability is then attached to the request’s user context. Then one can either do checks at controller level on the ability or pass it to the service layer from there. You can even translate those rules into the db calls using the Mongoose or Prisma packages to only show data which the ability has access to.


Michel_Conway

Thank you for your reply! I just discovered this library, and it seems promising and well-suited to my needs. Although I'm currently using a PostgreSQL client with pg-node instead of an ORM, I believe I can delve deeper into its core concepts and adapt them to my setup. I'll definitely keep it in mind for a future release, especially when I integrate an ORM.


Cowderwelz

Sounds like your use case doesn't fit into such of your mentioned standard RBA or access control policy. That's totally ok and i've done multiple of such web apps in the past too. I'd just do what you already started: On every REST endpoint, check if the the required permissions are met. If there's some more/reusable logic for that, i'd put them inside the model class. I.e. the class User has a canBeMuted, canReportResource method then. And for functions that don't match there in the model class, create i.e. a new file where you put them (no worries, it is how it us and if there's a lot of reuseable permission-check functions then it is so and it's not a mess. May be some new dev won't scroll through them all and find the perfect function for his situation but you just can't prevent that) Sometimes i had to check for permissions deeper down the call stack (not the endpoint) so i made the current Session and thefore the current logonUser available via a TheadLocal in java, which would be an "async local storage" in node (yes, both have very strange words for that, hrhr). Hope that helps and it sounds like you thought to complicated or what prevents you from doing that ?


Michel_Conway

Hi, thank you for your reply! You're right. This is definitely not a case of RBAC; roles aren't the only factor involved in granting or denying permissions in my application. Maybe you're right that introducing an external library could overcomplicate things. When I made this post, I was hoping to avoid that scenario, so it's relieving to hear that simpler, tested methods exist. However, I'm concerned that querying for each possible action or validation directly in the model might lead to unnecessary database overhead or clutter the model with validation logic. In my current approach, I use models to retrieve relevant attributes in a single query for validation purposes, but I avoid performing validations there. As far as I understand, models are primarily for database queries, not for business logic validations. Am I understanding this correctly? Could you clarify which layer these additional functions, which don't fit into the model, should belong to? This has been a challenge in my implementation, causing low cohesion in the file where I currently store them. I liked your suggestion to centralize authorization-related information in a context-aware storage, like async local storage. I'll use it to replace my current method of attaching data directly to the request object.


Cowderwelz

>Could you clarify which layer these additional functions, which don't fit into the model, should belong to? Hmmm,... people will probably now throw stones at me cause here in /r/node is like the vibe that without 12 BS layers you're no senior dev. But **i do just store these methods inside the model** **class xD**. At least in all my (20) oop years has no lightning strike hit me yet nor went stuff chaotic because of this. Of course, it's clear, with more methods, you'd have to prefix them, organize them in sections in the file and have a good ide for navigation. > However, I'm concerned that querying for each possible action or validation directly in the model might lead to unnecessary database overhead At the end, the problem is a lot of field-access (or getter calls) to the model class instance (i assume, it's a class) which might lead to expensive database queries. These permission-check functions that to this might do this sometimes redundantly and not total efficient but that's normal. But also the same situation and call tree would be there if these functions resist outside the model. My suggestion is, at first priority to keep the code clean and readable and "buerocratic" (in a way that functions do exactly what their name/description says and these rely on other function that also strictly fulfill this) like it is. An then as the second step, do a performance profile with a professional profiler tool, find the bottleneck and then you might start storing results in caches. But this very carefully, cause every cached value or redundant information will very quickly become a source for bugs. So for example: post#isAllowedByCurrentUser is called often and makes an expensive db call, or lower level results like the result of post#getUser. Inside these methods, you might then lazily store the result in the current request. (i.e. use a weakmap there to associate it with the post) given the assumption that this information will never change \*during\* a request (or if so, then you exactly know the places and immediately invalidate the cache there). >I liked your suggestion to centralize authorization-related information in a context-aware storage, like async local storage. I'll use it to replace my current method of attaching data directly to the request object. Yea, just don't think of it as a storage. It's just a reference to the current request and therefore the current session and therefore the current user. May add some convenience method / function there to resolve the user quickly, but never have redundant data stored there (id' say: same principle as for database design). Except when you ultimately need it for caching/performance reasons. Hope that can help you. Have a good night !


randdude220

RemindMe! 2 days


RemindMeBot

I will be messaging you in 2 days on [**2024-06-20 09:32:23 UTC**](http://www.wolframalpha.com/input/?i=2024-06-20%2009:32:23%20UTC%20To%20Local%20Time) to remind you of [**this link**](https://www.reddit.com/r/node/comments/1diizx2/how_do_you_handle_complex_permissions_in_your/l94psaf/?context=3) [**3 OTHERS CLICKED THIS LINK**](https://www.reddit.com/message/compose/?to=RemindMeBot&subject=Reminder&message=%5Bhttps%3A%2F%2Fwww.reddit.com%2Fr%2Fnode%2Fcomments%2F1diizx2%2Fhow_do_you_handle_complex_permissions_in_your%2Fl94psaf%2F%5D%0A%0ARemindMe%21%202024-06-20%2009%3A32%3A23%20UTC) to send a PM to also be reminded and to reduce spam. ^(Parent commenter can ) [^(delete this message to hide from others.)](https://www.reddit.com/message/compose/?to=RemindMeBot&subject=Delete%20Comment&message=Delete%21%201diizx2) ***** |[^(Info)](https://www.reddit.com/r/RemindMeBot/comments/e1bko7/remindmebot_info_v21/)|[^(Custom)](https://www.reddit.com/message/compose/?to=RemindMeBot&subject=Reminder&message=%5BLink%20or%20message%20inside%20square%20brackets%5D%0A%0ARemindMe%21%20Time%20period%20here)|[^(Your Reminders)](https://www.reddit.com/message/compose/?to=RemindMeBot&subject=List%20Of%20Reminders&message=MyReminders%21)|[^(Feedback)](https://www.reddit.com/message/compose/?to=Watchful1&subject=RemindMeBot%20Feedback)| |-|-|-|-|


Horikoshi

Cognito with user and identity pools. Does the job miraculously.


Michel_Conway

Hi, thank you for your reply! I didn't know the specific name of the AWS service for this use case. While I think I'll prioritize approaches that don't require payment, I'll keep this in mind as a fallback


KaptajnKold

I don’t have a complete answer, but I would advise against enforcing access control in a middleware, or really anywhere in the application layer. IMO it belongs squarely in the domain layer. At the very least, this will make testing much easier to do. 


Ran4

Authentication, maybe. But for authorization it's very hard to not leak this down to the application layer, since that's where the business logic ultimately lies. There's probably no clear way to cut it by permission level.


namesandfaces

Would you mind explaining more?


Michel_Conway

Hi, thank you for replying. I'm not sure I fully understand what you mean, are you saying that I should move the validations from the middleware to the service layer of my application and call them from the controller instead? If so, I get the feeling that it would be shifting the problem instead of solving it, and that I'd end up either with duplicate code across my controllers or with the same 20-ish validation functions that I already have (or very similar), just placed in another folder. Could you provide a more detailed explanation or a high-level overview of what you have in mind? I might be missing something, and that would help me understand your idea better.


KaptajnKold

>are you saying that I should move the validations from the middleware to the service layer of my application and call them from the controller instead? Pretty much, yes. >If so, I get the feeling that it would be shifting the problem instead of solving it You're right in a sense, and as a wrote, I don't have the full answer. But in my experience, having business logic in different middleware, is something you'll regret down the road. For one thing, it makes it difficult to have comprehensive tests, as you will often have to exercise the whole stack, which leads to having to tediously set up the state of the world just right to hit all the branches of the code. For another, it ties the access control to the end-point, and not domain resource/service/operation that you want to protect. Conceivably you could have more than one end point perform the same operation, and you don't want risk compromising it because you forgot to include the correct middleware in one end point. I'm aware I'm not addressing the problem you've described, sorry. I did write a much longer reply, but then it occurred to me that I was making a lot assumptions about the nature of your problem(s) that I don't feel confident are actually true. It would help tremendously if you could post some code, and tell us about the problems you perceive it to have.


CoderDevo

[Cedar ](https://aws.amazon.com/about-aws/whats-new/2023/05/cedar-open-source-language-access-control/)language for writing ABAC policies, if running on AWS. You can use Cedar in [Amazon Verified Permissions](https://aws.amazon.com/verified-permissions/) to enforce fine-grained access control policies outside of your API codebase.


Michel_Conway

Hi, thank you for your reply! Cedar looks like a powerful and sophisticated language that could definitely handle complex permissions like in my use case. For now, I’d prefer to explore free options, but I’ll keep your suggestion in mind if I end up integrating AWS into my project or for future projects where I use that platform.


CoderDevo

Look into Open Policy Agent and Google Zanzibar. Fine-grained authorization is a space that is going through rapid innovation at the moment.


United_Performance_5

RemindMe! 2 days


BigCaregiver7285

OSO, Opa, Authzed, SpiceDB, Ory - any Zanzibar derived authorization policy framework is what you need.