2

My question may sound very naïve to someone, but it is what it is.

I have below scenario:

  • Relational MySQL Database with BIGINT primary keys and foreign keys
  • Spring boot as a backend (technology doesn't matter here)
  • Angular as a frontend (again technology doesn't matter)

This system is a large system and there are many intermingled modules with foreign key references, lookup tables, etc. and all the users must be authenticated (we have Spring Security OAuth) to access this system. So from authentication view-point, we are safe. Also we are managing roles and permissions for user's actions (they are mostly used on UI to hide/show menus, buttons, and actions). Everything good so far. The real challenge arises here - lets say there is some ABC module, and authenticated user X is accessing its information using REST call /api/abc/1, here 1 is the primary key of ABC, and ABC data with key 1 belongs to X only. But as you can see here, changing the id in above URL, user can access ABC module data of other users too. So how can these actions be authorized?

Below are the points of my research, but none of them seem viable:

  • Use UUID instead of Int primary keys. But there are drawbacks of UUIds that they increases the database size and they are also guessable at some point by bots.
  • Do not expose the primary keys. But this case will not work in my scenario, as I told that there are many intermingled modules with many references. And in many of the modules, primary keys are the one which are unique.
  • Intercept every REST call, and check whether the passed id/ids belong to that particular authenticated user. But this solution will also become clumsy after some extends because there are many modules, and it will also increase the number of queries to database.

So what is the best way for authorization in this kind of application?

NOTE: This question is not related to any of the technology or implementation, but just related to Security design of REST APIs.

3
  • 5
    "My question may sound very naïve(...)". There are a plethora of examples of security incidents that were made possible because someone forgot to think about this. Good on you for asking this question, it is not naïve. Too many others ignore such things, or pretend they are not real issues. That is naïve.
    – Kjartan
    Dec 21, 2020 at 8:06
  • Option 3 is the best and most used way to authorize API requests
    – Ishan Shah
    Dec 22, 2020 at 6:43
  • I would recommend to use a DB in memory like Redis to store your authentication with authenticaion user info Jan 3, 2021 at 6:34

2 Answers 2

3

You need to implement number 3:

Intercept every REST call, and check whether the passed id/ids belong to that particular authenticated user. But this solution will also become clumsy after some extends because there are many modules, and it will also increase the number of queries to database.

Since you only have one RDBM to query against it seems like a totally easy and in-expensive join on the users or permissions table.

You always need a WHERE on your SQL statement to determine whether the user "owns" the data or not.

10
  • But there will not be always the straight forward case where I will get userId in each and every table which user owns. There might be intermediate tables connecting user and that particular table. Dec 21, 2020 at 7:15
  • Or there might be the case where user actually does not owns that particular resource but has been given the permission to access it, Dec 21, 2020 at 7:19
  • 1
    Ownership or permission, it doesn't really matter. You have to make a look up on the particular resource to determine whether access can be granted or not. Dec 21, 2020 at 7:22
  • Yeah, but that will become clumsy and joins will increase, as the application expands as number of resources increase. Dec 21, 2020 at 7:23
  • 1
    No. The joins for the permissions or users tables will always be pretty much the same. If you cannot join a e.g Product table with a users or permissions table in one join, something else is wrong in your model. Dec 21, 2020 at 7:25
2

Like @DanielFrost, I too would tend to recommend 3, but there are some other options

  1. Implement row-level security on your database. This may or may not be feasible depending on how decoupled your API, DB, authentication mechanism is and what products you are using.

  2. Encrypt the IDs on the way out using the user ID (or proxy) + session salt, essentially rendering them opaque and unique to a user + session. Decrypt them back to native when they are submitted in the API URL.

The advantage of #2 is that you don't have to do all the joins, though you still have to do some work on each endpoint that needs the ID.

4
  • I have thought of #2, and that will work fine. But it will cause issue in case of micro-services which are in different technology, and when data from one micro-service is fetched on UI and is sent to another micro-service. What can be done in that case? Dec 22, 2020 at 4:37
  • You need to apply the same technique consistently at every end-point. If you don't do that then obviously it's not going to work even with the same implementation technology. Dec 22, 2020 at 4:53
  • Yeah I understand that I must have to use same encryption technique and algorithm everywhere, but my concern is will it produce consistent results with all the technologies (i.e. encrypted in one and decrypted in other)? Dec 22, 2020 at 5:11
  • If you write it that way then yes! Why wouldn't it? Dec 22, 2020 at 5:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.