Real World Example

Order Ownership at Swiggy-Style Apps

In a food or e-commerce app, the most common security bug is IDOR (Insecure Direct Object Reference): changing /orders/1001 to /orders/1002 and seeing a stranger's address and phone number. Every app like Swiggy, Zomato or Flipkart protects order, address and payment endpoints with an ownership check. In Spring, that check is method security.

@Service
public class OrderService {

    private final OrderRepository orders;

    public OrderService(OrderRepository orders) { this.orders = orders; }

    // customers: only their own order; support staff: any order
    @PreAuthorize("hasAuthority('order:read:any') or @orderSecurity.isOwner(#orderId, authentication)")
    public OrderDto details(Long orderId) {
        return orders.findById(orderId).map(OrderDto::from)
                .orElseThrow(() -> new OrderNotFoundException(orderId));
    }

    // refunds above Rs 5000 need the finance permission
    @PreAuthorize("hasAuthority('order:refund') and (#amount <= 5000 or hasAuthority('refund:large'))")
    public RefundDto refund(Long orderId, BigDecimal amount) {
        // ... call payment gateway, save refund
    }

    // list endpoint: filter in the query, not with @PostFilter
    @PreAuthorize("isAuthenticated()")
    public Page<OrderDto> myOrders(Authentication auth, Pageable pageable) {
        return orders.findByCustomerEmail(auth.getName(), pageable).map(OrderDto::from);
    }
}

Result

Example

A support agent with order:refund (but not refund:large) refunds Rs 1,200, then tries Rs 25,000:

curl -i -X POST -u [email protected]:pass -H "Content-Type: application/json" \
  -d '{"amount":1200}' localhost:8080/api/orders/1001/refund
curl -i -X POST -u [email protected]:pass -H "Content-Type: application/json" \
  -d '{"amount":25000}' localhost:8080/api/orders/1003/refund

Example output

HTTP/1.1 200 OK
{"orderId":1001,"amount":1200.0,"status":"REFUND_INITIATED"}

HTTP/1.1 403 Forbidden