Bola Idor in Spring Boot with Basic Auth
Bola Idor in Spring Boot with Basic Auth — how this specific combination creates or exposes the vulnerability
Broken Object Level Authorization (BOLA) occurs when an API exposes one user’s resource to another by failing to enforce authorization checks at the object level. In a Spring Boot application using HTTP Basic Auth, BOLA often arises when authentication confirms identity (who you are) but the application skips or weakly validates whether the authenticated user is allowed to access a specific resource (what you can do).
Consider a typical setup where Spring Security secures endpoints with Basic Auth and a controller maps URLs like /users/{userId}. If the controller loads a user by ID from the repository without verifying that the authenticated principal matches that ID, an attacker can change the userId path variable to access or modify other users’ data. This is BOLA at the object level: the API authenticates the request but does not ensure ownership or permission on the object being accessed.
Basic Auth transmits credentials on every request encoded but not encrypted in transit; if used without TLS, credentials are easily intercepted. Even with TLS, the risk remains if the server does not couple authentication with per-request authorization. For example, an endpoint returning a user profile by ID should confirm that the authenticated username or ID matches the requested resource. Without this check, the endpoint is vulnerable to BOLA regardless of Basic Auth being present.
Spring Data REST can also inadvertently expose BOLA if repositories are exported directly without method-level security. Repository links might allow traversal to related objects (e.g., /users/{userId}/orders) and if each order is fetched by ID without verifying the order belongs to the authenticated user, attackers can iterate through known IDs to access other users’ orders. The presence of Basic Auth does not prevent this; the authorization logic must explicitly bind the authenticated subject to the domain object being accessed.
Real-world attack patterns mirror this: an authenticated user modifies userId=123 to userId=124 in a GET or PUT request and observes whether data is returned or updated. If no ownership verification exists, this constitutes BOLA (often tracked as CWE-285). In Spring Boot, this commonly maps to the OWASP API Top 10 category 1: Broken Object Level Authorization and can expose sensitive data or enable privilege escalation when combined with other issues.
Proper defense requires validating that the authenticated identity matches the resource identity on every request that accesses a specific object. This means loading the domain object and comparing its owning user or scope to the authenticated principal, not merely checking that a user is logged in.
Basic Auth-Specific Remediation in Spring Boot — concrete code fixes
To mitigate BOLA with Basic Auth in Spring Boot, couple authentication with explicit authorization checks and prefer method-level security. Below are concrete, working examples that demonstrate secure patterns.
1. Secure controller with ownership check
Always resolve the authenticated name and compare it to the resource owner before proceeding.
@RestController
@RequestMapping("/users")
public class UserController {
private final UserRepository userRepository;
public UserController(UserRepository userRepository) {
this.userRepository = userRepository;
}
@GetMapping("/{userId}")
public ResponseEntity<UserDto> getUserById(
@PathVariable UUID userId,
@AuthenticationPrincipal(expression = "name") String authenticatedUsername) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found"));
// BOLA prevention: ensure the authenticated user matches the resource owner
if (!user.getUsername().equals(authenticatedUsername)) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "Access denied to this resource");
}
return ResponseEntity.ok(new UserDto(user));
}
}
2. Method-level security with expressions
Use Spring’s @PreAuthorize to enforce ownership declaratively. This keeps authorization close to the domain operation.
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@PreAuthorize("#userId == authentication.name")
public UserDto getUserById(UUID userId) {
return userRepository.findById(userId)
.map(UserDto::from)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
}
}
Enable method security in your configuration:
@Configuration
@EnableMethodSecurity
public class SecurityConfig {
// other security configuration
}
3. Repository-side scoping with SecurityFilterChain
Define a security filter chain that enforces HTTPS and ensures authentication, while relying on service-level checks for object ownership.
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/users/**").authenticated()
.anyRequest().denyAll()
)
.httpBasic(Customizer.withDefaults())
.csrf(AbstractHttpConfigurer::disable);
return http.build();
}
4. DTO projection to avoid over-fetching
Project only necessary fields and keep sensitive data scoped to the authenticated user.
@Repository
public interface UserRepository extends JpaRepository<User, UUID> {
@Query("SELECT u.username as username, u.email as email FROM User u WHERE u.id = :id AND u.username = :principal")
Optional<UserProjection> findByIdAndUsername(@Param("id") UUID id, @Param("principal") String principal);
}
Use the projection in the controller to guarantee row-level ownership at the query level.
5. Validating related resources (e.g., orders)
When accessing associations, verify ownership through the owning entity.
@GetMapping("/users/{userId}/orders/{orderId}")
public ResponseEntity<OrderDto> getOrder(
@PathVariable UUID userId,
@PathVariable UUID orderId,
@AuthenticationPrincipal(expression = "name") String authenticatedUsername) {
Order order = orderRepository.findByIdAndOwner(orderId, authenticatedUsername)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Order not found or access denied"));
return ResponseEntity.ok(new OrderDto(order));
}
Ensure your repository defines the custom query findByIdAndOwner to enforce ownership at the database level.
Related CWEs: bolaAuthorization
| CWE ID | Name | Severity |
|---|---|---|
| CWE-250 | Execution with Unnecessary Privileges | HIGH |
| CWE-639 | Insecure Direct Object Reference | CRITICAL |
| CWE-732 | Incorrect Permission Assignment | HIGH |