The labs on this site need JavaScript to run. The same content is written out below.
Learn › Security › Authorization
Access control
If a logged-in user only changes the number in the URL, can they see someone else's order?
Being logged in only means that "who you are has been confirmed" (authentication). It does not mean "you may view this resource" (authorization). What happens if kim logs in and then changes only the order number in the URL to request lee's order? Depending on what the server checks, the order is either shown as it is or the request is refused.
Checked against real execution · A Spring Boot 4.1.1 and Spring Security 7.1.1 server was started on 127.0.0.1, and the response codes and bodies were checked for all 30 combinations
Concepts in this lab
- Authentication and authorization
- Authentication checks who a user is. Authorization decides whether that verified user has permission to use a specific resource or feature. Authentication happens first, and authorization is decided based on its result. In HTTP status codes, a failed authentication corresponds to 401 and a failed authorization corresponds to 403.
- Access control and ownership checks
- Access control is the job of having the server decide, and enforce, which resources a verified user may view or change. If the server checks only that the user is logged in and does not check who owns the resource, one user can view another person's resource by changing just a number in the URL or the request. This kind of vulnerability is called Insecure Direct Object Reference (IDOR). To prevent it, after looking up the resource, the server code compares the owner of the resource with the user who is logged in now, or it looks up the resource only "among the user's own" from the start. Hiding a button or a link on the screen is not access control, and the check must always be made on the server, on every request.
- Role-based access control (RBAC)
- A method in which users are given roles such as USER and ADMIN, and what each role may do is decided in advance. In Spring Security, you write the role that is needed for a URL or a method, as in hasRole("ADMIN"). The rules are simple and easy to manage in one place, but a role only says "what kind of user this is", so it cannot tell apart two users who have the same role. That is why a rule such as "you can view only your own orders" cannot be expressed with roles alone, and you must use it together with a check that compares the owner of the resource.
- HTTP status codes
- A three-digit number that shows the result of the server's handling of a request. 2xx means success, 3xx means a move to another location (redirection), 4xx means a problem with the client's request, and 5xx means an error on the server side. Common ones are 200 (success), 201 (created), 400 (bad request), 401 (authentication required), 403 (no permission), 404 (not found), and 500 (server error).
- Spring Security and SecurityFilterChain
- Spring Security is Spring's security framework for handling authentication and authorization, and it works as a set of Servlet filters. A SecurityFilterChain defines which security filters apply to which requests and in what order. Before a request reaches the controller, the filters run one after another and do work such as checking the authentication information and checking permissions.
Try these
- Default settings — The server checks only that the user is logged in. Changing just the number shows lee's order as it is.
- Blocking by role — kim is also USER, so the request passes. A role does not know whose order it is.
- Checking the owner — The owner of the order and the logged-in user differ, so the request is refused with 403.
- Entering a number that does not exist — The response is 404. Order 2 gave 403, so it becomes known that order 2 exists.
- Answering 404 for other people's orders too — It cannot be told apart from an order that does not exist, so you cannot even tell whether the order exists.
- Customer support staff looks up the order — Even without being the owner, the ADMIN role allows the request.
- When the role rule blocks the staff — The response is 403 before the controller even runs.
Running with the default settings
- kim, logged in, sends GET /orders/2. kim's order is order 1, and order 2 is lee's order. This is the URL with only the number changed to 2.
- Authentication: the server checks the login information carried in the request and finds out that the sender is kim with the role USER. So far it has only confirmed "who this is". No one has yet asked whether order 2 may be viewed.
- .anyRequest().authenticated(): the URL rule looks only at whether the user is logged in. kim is logged in, so the request passes. This rule does not know whose order the number 2 at the end of the URL refers to.
- orders.findById(2): the controller looks up the order by number. lee's order comes back.
- return o: the controller returns the order it looked up as it is. Nowhere in the code is the owner of the order (lee) compared with the logged-in user (kim).
- The response is 200 OK, and the body contains lee's order (the product and the delivery address), which goes to kim. The login was done properly and the request passed every rule on the server, yet someone else's order became visible.
Result of this run
- Authentication (confirming who the user is) and authorization (confirming that the user may view this resource) are different jobs. On a server that checks only for login, you can see someone else's resource just by changing the number in the URL. If the numbers run in sequence like 1, 2, 3, simply trying them one after another reveals every other person's order. Before returning a resource, the server code must check "is the owner of this resource the user who is logged in now?".
Code
// The owner of order 1 is kim, and the owner of order 2 is lee. Order 99 does not exist.
// The role of kim and lee is USER, and the role of park is ADMIN.
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(a -> a
.anyRequest().authenticated()) // checks only that the user is logged in
.httpBasic(Customizer.withDefaults()) // how to log in (the result is the same whichever you use)
.build();
}
@GetMapping("/orders/{id}")
Order one(@PathVariable long id, Authentication auth) { // auth: the user logged in now
Order o = orders.findById(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
return o; // does not check whose order it is
}
Assumptions of this model
All 30 combinations were checked against real execution on the PC used to write this lab. A Spring Boot 4.1.1 and Spring Security 7.1.1 server was started on 127.0.0.1 with code of the same structure as on the screen, and the checks were the response code, the owner of the order in the response body, whether the controller ran, and which branch of the code made the decision. The order storage was made as an in-memory table instead of a database, and HTTP Basic was used to log in (the authorization result is the same if you log in with a session or a token). On the same server, a request that is not logged in got 401 with every approach, and that is covered in the "Security filter chain" lab. That a server can answer 404 instead of 403 for another person's resource, to hide the fact that the resource exists, is written in RFC 9110 Section 15.5.4 (403) and Section 15.5.5 (404). Which of 403 and 404 is right is not fixed; the service chooses. In a real service, the existence of a resource can also be revealed by a difference in response time, not only by the response code, but this lab does not cover that. The owner check was written in the controller, but it is also placed in the service layer or written with method security (@PreAuthorize, @PostAuthorize), and that continues in the "Method security" lab. Changing order numbers to values that are hard to guess (such as UUIDs) only makes the numbers harder to guess and does not replace an owner check. This sentence follows a general security recommendation (OWASP's description of Broken Access Control) and is not something that was checked by execution.
Previous lab: Security filter chain · Next lab: Method security