randomdev
randomdev

Reputation: 100

Spring Boot - make sure data belongs to current logged in user

I have a Spring Boot REST API that I'm building. Im slightly stuck on the correct way to design my API in a way that protects each individual users' data. For example, consider the following database relations:

User -> (Has Many) Projects -> (Has Many) Tasks. (A User has-many Projects, and a Project has-many tasks).

For example, if I design my endpoints in the following way:

GET /api/v1/projects/{projectId}

POST /api/v1/projects/{projectId}/tasks

Just as a simple example for the above, how can I make sure, when creating new tasks for a certain project, that the project belongs to the logged in user?

Currently, I am using JWT tokens via Spring Security as my authentication strategy, and included in the payload of the token I have my Users' id. So with every request I can retrieve the user, but surely that's incredibly inefficient to be making so many requests to the database and check if the user actually has a given project.

Some solution I was thinking about is to simply have endpoints designed like this:

/api/v1/users/{userId}/projects/{projectId}/tasks

And then I can use the user id in the JWT payload and compare it to the user id in the request parameter. But then that would mean with every new relation in my database, the length of the url is going to be massive :) Also I guess it would mean all the business logic would be inside the User service for the whole application, right? Which seems a little odd to me... but maybe I'm wrong.

Im not sure if thats an issue or not, but just trying to design the API to be as elegant as possible.

Thanks again!

Upvotes: 5

Views: 3406

Answers (2)

tima
tima

Reputation: 1513

Checking if the user has permissions to a project on every request is the correct solution. Consider cases when many other applications / users are calling your API. You want to make sure that your API is as secure as possible and cannot be manipulated from the frontend.

To make it more efficient you should have a way/query to check associations in your database like a simple query that returns true/false which should be quicker than retrieving all the data and comparing in Java code.

And when possible combine multiple database queries into one, like for one of your examples:

GET /api/v1/projects/{projectId}

in this case, don't run a query to get a user's information and a query for the requested project. Instead you could do a single query with a join between the user's table and the project table which should only return a project if the user is associated with it. The best way really depends on how your database is structured.

Adding a user id into the API URL is just redundant information. Just because the user id in the token matches the user id in the URL doesn't mean the user has any kind of permissions to any project.

Another solution to be avoided is to include the user's project ids in the JWT token which you can then compare without making a database request. This is bad for several reasons:

  1. The token should only have required information for the user to access the API, it shouldn't have business logic
  2. Depending on how much business logic you store in the token the token can become large in size. See this post for a discussion on size limits: What is the maximum size of JWT token?
  3. If there is a way for the someone other than the user (like admin) to add/remove a user's association to a project then that change will not be reflected in the token until the token's data is refreshed

EDIT:

On the spring side I have used the @PreAuthorize annotation before to handle these types of method checks. Below is pseudo code as an example.

@RestController
public class MyController {

    @PostMapping
    @PreAuthorize("@mySecurityService.isAllowed(principal, #in)")
    public SomeResponseType api1(SomeRequestType requestData) {
        /* this is not reached unless mySecurityService.isAllowed
        returns true, instead a user gets a 401/403 HTTP response 
        code (i don't remember the exact one) */
    }
}

@Service
public class MySecurityService {

    /*
    Object principal - this is spring's UserDetails object that is
    returned from the AuthenticationProvider. So basically a Java
    representation of the  JWT token which should have the 
    user's username.

    SomeRequestType requestData - this is the request data that was 
    sent to the API. I'm sure there is a way to get the project ID 
    from the URL here as well.
    */
    public boolean isAllowed(Object principal, SomeRequestType requestData) {
        /*
        take the user's username from the principal, take the 
        project ID from the request data and query the database 
        to check authorization, return true if authorized

        make this check efficient
        */

        return false;
    }
}

The annotation and the security service can then be applied to multiple methods. You can have many different security services depending on what your are checking.

There are other ways available too https://www.baeldung.com/spring-security-method-security and this has to be enabled in spring's configuration (also explained in the link).

Upvotes: 4

Vyktor
Vyktor

Reputation: 26

Hi so if I understood it correctly you want to automatically assign the task that is going to be created with "POST /api/v1/projects/{projectId}/tasks" to the current logged in user.

You could try to add a Parameter 'Principal principal' to your rest controller. The Principal is the user that is sending the request.

After you have your Prinicipal, you could write a simple convert method(for example: convertPrincipalToUser(Principal principal) which returns you the user. Finally you can add your user to the corresponding task)

Here is some more information about it: https://www.baeldung.com/get-user-in-spring-security

Upvotes: 0

Related Questions