Spring Boot

Capstone – Article 10: Securing Expenses to the Logged-In User (JWT Context)

By Utility Zone · 2026-01-27T18:33:02.004595

1. Introduction

So far, expense APIs work — but they are not fully secure yet.

Problem:

  • We used a temporary hardcoded userId
  • This breaks real-world security rules

In this article, we will:

  • Extract logged-in user identity from JWT
  • Resolve the user from database
  • Ensure expenses are always tied to the authenticated user

This step is critical for data security.


2. Why This Step Matters

Without proper user binding:

  • Users could access others’ data
  • Authorization becomes meaningless
  • Application fails security reviews

A secure backend must: ✔ Trust JWT, not request data
✔ Enforce ownership at service layer


3. Getting Logged-In User from SecurityContext

Spring Security stores authentication details in:

SecurityContextHolder.getContext().getAuthentication()

From our JWT filter, we already set:

  • Principal = email
  • Authorities = roles

We will now use this email.


4. Creating Security Utility Helper

Create package:

com.example.expensetracker.security

Create class:

package com.example.expensetracker.security;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;

public class SecurityUtil {

    private SecurityUtil() {}

    public static String getCurrentUserEmail() {
        Authentication auth =
            SecurityContextHolder.getContext().getAuthentication();

        return auth != null ? auth.getName() : null;
    }
}

This gives us the email from JWT.


5. Resolving User from Email

Update UserRepository (already exists):

Optional<User> findByEmail(String email);

We’ll use this to fetch the logged-in user.


6. Updating ExpenseService

6.1 Remove userId Parameter

Before (❌):

createExpense(Long userId, ExpenseRequest request)

After (✔):

createExpense(ExpenseRequest request)

6.2 Secure Expense Creation

@Service
public class ExpenseService {

    private final ExpenseRepository expenseRepository;
    private final UserRepository userRepository;

    public ExpenseService(ExpenseRepository expenseRepository,
                          UserRepository userRepository) {
        this.expenseRepository = expenseRepository;
        this.userRepository = userRepository;
    }

    public ExpenseResponse createExpense(ExpenseRequest request) {

        String email = SecurityUtil.getCurrentUserEmail();

        User user = userRepository.findByEmail(email)
            .orElseThrow(() -> new UserNotFoundException("User not found"));

        Expense expense = new Expense();
        expense.setAmount(request.getAmount());
        expense.setDescription(request.getDescription());
        expense.setCategory(request.getCategory());
        expense.setExpenseDate(request.getExpenseDate());
        expense.setUser(user);

        return mapToResponse(expenseRepository.save(expense));
    }

    public List<ExpenseResponse> getMyExpenses() {

        String email = SecurityUtil.getCurrentUserEmail();

        User user = userRepository.findByEmail(email)
            .orElseThrow(() -> new UserNotFoundException("User not found"));

        return expenseRepository.findByUserId(user.getId())
            .stream()
            .map(this::mapToResponse)
            .toList();
    }
}

✔ No hardcoded IDs
✔ JWT identity enforced
✔ Ownership guaranteed


7. Updating ExpenseController

@RestController
@RequestMapping("/expenses")
public class ExpenseController {

    private final ExpenseService expenseService;

    public ExpenseController(ExpenseService expenseService) {
        this.expenseService = expenseService;
    }

    @PostMapping
    public ExpenseResponse createExpense(@Valid @RequestBody ExpenseRequest request) {
        return expenseService.createExpense(request);
    }

    @GetMapping
    public List<ExpenseResponse> getMyExpenses() {
        return expenseService.getMyExpenses();
    }
}

Controller is now completely clean.


8. Testing Secure Ownership

Scenario 1: User A

  • Login as User A
  • Create expense
  • Fetch expenses → sees own data

Scenario 2: User B

  • Login as User B
  • Fetch expenses → sees only their data

No overlap allowed.


9. What Happens If Token Is Missing?

  • No JWT → 401 Unauthorized
  • Invalid JWT → 401
  • Valid JWT → access granted

Spring Security handles this automatically.


10. Common Mistakes

❌ Passing userId in request
❌ Trusting frontend user info
❌ Accessing SecurityContext in controller
❌ Hardcoding user references


11. Git Commit (Important)

git add .
git commit -m "Secure expense APIs to logged-in user via JWT"

12. What You Should Have Now

At this point:

  • Expenses are user-scoped
  • JWT identity is enforced
  • Security is real and robust

This is real-world backend behavior.


13. What’s Next?

➡ Capstone – Article 11: Expense Categories & Advanced Queries

  • Category management
  • Filtering by date/category
  • Pagination

Type Next when you’re ready 🚀