skip to Main Content

I’m creating a project where a user can login or signup, but I just found out a security flaw.

First let me tell the flow when a user received a token from the server,

A user can signup or login, if all validation are correct then server will response with:

{
  "meta": {
    "issueDate": 1592078419167,
    "expToken": "10800000"
  },
  "payload": {
    "user": {
      "id": 3,
      "email": "[email protected]",
      "username": "new3"
    },
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MywiZW1haWwiOiJuZXczQGdtYWlsLmNvbSIsInVzZXJuYW1lIjoibmV3MyIsImlhdCI6MTU5MjA3ODQxOSwiZXhwIjoxNTkyMDg5MjE5fQ.cjLgQjc2QbIhgumG2X2VsLk70c58F7NrKboYL2F2Sa0"
  }
}

Then with that token he can do a CRUD operations by attaching it into the header as Authorization: 'bearer TOKEN' schema. With the token in place a middleware in the server checks if it was tampered or expired (I’m using JWT here) if it’s expired or tampered, then the server response with 401 unauthorized.

What I notice though is this. Lets say I have tables USERS and POSTS (1:N). User1 has 2 posts with id of 1 and 2 respectively, then another user signs up, lets call him User2, User2 signs up successfully received a token then goes to terminal uses curl, sends a request to api/post/1/edit route which he doesn’t own. This is my problem right here. A user with a knowledge how internet/API works can just grab a token and access any resource.

I have few solutions in mind.

  1. create a middleware which checks if the user owns the resource by querying database. But I don’t think this is a performant solution because I have to query and do it on different controller, like users_controller, posts_controller, another_controller.

  2. instead of incrementing ID, I will use UUID so whenever they try to access posts edit route they cant easily figure out the ID. like api/post/1akA13124S5129/edit <— this is hard to figure out.

PS: I tried on Facebook, but it doesn’t work. I think they have a lot of token and cookies. Incidentally, I don’t use/include any cookies on any request for now.

2

Answers


  1. Definitely number one. That’s what Facebook, StackExchange, Google, Microsoft, and most popular sites use for securing their API.

    I will use UUID so whenever they try to access posts edit route

    When did they get those UUIDs in the first place? You’re essentially generating one token for each resource. That way, when the user simply wants to see today’s posts, your backend will still end up querying the database for generating/selecting the UUID for each post, even though the user isn’t likely going to update every single post they’re looking. Oh, you want to delay the UUID until the user actually calls the edit route? Since the user still needs to get that UUID in the first place, then it’s just an additional “pass UUID to user” step that doesn’t add any security.

    Then this is also becoming a headache if a resource isn’t completely secret and only usable for one user. On Facebook, a personal post can only be deleted/edited by the submitter, but others can comment or react to it. Since Facebook needs to keep track who reacts what, then they’ll still end up check the auth token and correlate with the user table. Your UUID method meanwhile, would end up creating multiple UUID for each different user, so if say a post is being seen by hundreds of users, you end up with storing hundreds of UUID for that post alone. Then consider comments (which can be deleted by the commenter and the post creator), page/group post (which can be deleted by the page/group admin), or even this very site, where questions & answers can be edited by anyone.

    Login or Signup to reply.
  2. You’ve devised two of the common solutions. The first is the simplest and most common. How many requests/second are you dealing with that you’re seeing performance problems with? I would first look into optimizing your database queries before you build a more complicated solution.

    But in some cases the second solution (a “sparse” identifier) is very handy. I use them all the time. The only important thing is that it means that knowing the identifier implies access. So you likely can’t have an URL like api/post/1akA13124S5129/edit. What would the “read, but don’t edit” URL look like? It can’t include “1akA13124S5129” since anyone who knows that identifier can edit the post by adding /edit.

    My favorite way to use “knowing the identifier grants access” is to use SHA-256 hash of the data as the identifier. That means that in order to guess the identifier, you need to know the contents already. This is a very nice way to handle things like images. But it doesn’t work for things you want to edit, like you’re doing here.

    If you really can’t do database lookups (and if not, you really should expand this question to explain your problem), then there are many other approaches, but they depend heavily on your precise requirements. For example:

    • If users can edit their own posts, but anyone can read any post, then make the URL define the permissions. For example: api/post/alice/1 is only editable by alice.
    • Instead of using JWT only for authentication (authC), also use it for authorization (authZ). For example, when you want to access api/post/1/edit, first you must connect to an authZ server and request a JWT. That server will do a database lookup, and then grant you a JWT specifically for that URL (or you could batch a group of URLs, or a permissions group like “administrator”). Then you can reuse that JWT many times, and the resource serving side of the system doesn’t have to revalidate your access rights. It can rely on the JWT until it expires. This can take substantial load off of the database.

    But unless your system is quite large and you’ve already done everything you can to optimize the database queries, I’d just query the database. That’s what they’re for.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search