Broken Access Control in Spring Boot with Bearer Tokens
Broken Access Control in Spring Boot with Bearer Tokens — how this specific combination creates or exposes the vulnerability
Broken Access Control occurs when API endpoints do not properly enforce authorization checks, allowing one user to access or modify another user’s resources. In Spring Boot applications that rely on Bearer Tokens (typically JWTs), this risk increases when token validation is performed but authorization logic is missing, incomplete, or misaligned with the application’s data model.
Spring Security provides mechanisms to authenticate requests with Bearer Tokens, but authentication does not imply proper authorization. A common pattern is to validate the token, extract claims (such as subject or roles), and then proceed without verifying whether the authenticated principal is permitted to access the requested resource. For example, an endpoint like /api/users/{userId} might authenticate a token and extract a user ID from a claim, but if the implementation does not compare that claim to the {userId} path variable, any authenticated user can enumerate or modify other users’ data by changing the path parameter.
This becomes more pronounced in designs that use opaque tokens or custom authorities where roles and scopes are embedded in the token. If authorization checks rely solely on role names (e.g., hasRole('ADMIN')) without contextual checks on the resource owner, horizontal privilege escalation can occur. Consider an endpoint that performs a database query like repository.findById(id) without scoping the query to the authenticated user. An attacker with a valid Bearer Token can iterate over IDs and access or delete data belonging to other users, which is a classic BOLA/IDOR scenario.
Insecure default method-level security expressions can also contribute to the problem. Using @PreAuthorize without precise SpEL expressions may fail to enforce row-level constraints. For instance, @PreAuthorize("#userId == authentication.principal.id") must be consistently applied; omitting it on any handler exposes the endpoint. Similarly, token introspection or validation filters might set the SecurityContext but still allow access to endpoints that should be restricted based on scopes or custom claims, especially when CORS or permissive method security configurations are in place.
Spring Boot applications that expose RESTful routes without explicit ownership checks or tenant scoping create conditions where Bearer Tokens grant access but do not enforce boundaries between users. The presence of a valid token is not sufficient; the framework must enforce that each token’s subject can only interact with resources they own or are explicitly granted to act upon. Without these checks, the API surface remains wide open to authenticated attackers who possess any valid Bearer Token.
Bearer Tokens-Specific Remediation in Spring Boot — concrete code fixes
To mitigate Broken Access Control when using Bearer Tokens in Spring Boot, combine robust token validation with explicit authorization checks that tie each request to the resource owner. Always scope data access to the authenticated principal and avoid relying solely on role-based checks.
Example: Securing a user-specific endpoint
Assume a REST endpoint that retrieves user profiles by ID. The fix involves extracting the subject from the validated JWT and ensuring the requested ID matches the subject.
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/{userId}")
@PreAuthorize("#userId == authentication.principal.id")
public ResponseEntity<UserDto> getUserById(@PathVariable String userId,
Authentication authentication) {
// Spring Security ensures authentication is present and principal matches the check above
User user = userService.findByIdAndUserId(userId, authentication.getName());
return ResponseEntity.ok(UserMapper.toDto(user));
}
}
In this snippet, authentication.getName() returns the subject (e.g., user ID or email) extracted from the Bearer Token claims. The SpEL expression ensures that the path variable {userId} must equal the authenticated principal’s identifier, preventing horizontal access violations.
Example: Scoped repository query
At the data layer, enforce ownership in the query itself. Do not rely on application code to filter results after retrieval.
@Repository
public interface UserRepository extends JpaRepository<User, String> {
@Query("SELECT u FROM User u WHERE u.id = :id AND u.ownerId = :ownerId")
Optional<User> findByIdAndOwnerId(@Param("id") String id, @Param("ownerId") String ownerId);
}
Then in service code:
@Service
public class UserService {
private final UserRepository repository;
public UserDto getUserById(String id, String ownerId) {
return repository.findByIdAndOwnerId(id, ownerId)
.map(UserMapper::toDto)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
}
}
This ensures that even if an attacker manipulates the path parameter, the database will not return a row where the owner does not match the token’s subject.
Global method security with consistent principal extraction
Enable global method security and use a consistent approach to extract user identifiers from Bearer Tokens. Configure JWT parsing in a OncePerRequestFilter or equivalent, and set the Authentication with authorities derived from token claims.
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);
return http.build();
}
}
With oauth2ResourceServer configured, Spring Security validates the Bearer Token and populates the Authentication. Your @PreAuthorize expressions can then safely reference authentication.principal to enforce ownership.
Additional best practices
- Always scope queries to the authenticated user’s ID or tenant identifier.
- Avoid method-level security that only checks roles without resource ownership.
- Validate incoming IDs for format and ownership before using them in queries.
- Use consistent identifiers (subject/sub) across tokens and database records.