DEV Community

Cover image for From Email to User ID: A Security-Driven Refactor in Spring Boot Backend - Finovara
Marcin Parśniak
Marcin Parśniak

Posted on

From Email to User ID: A Security-Driven Refactor in Spring Boot Backend - Finovara

At some point, a backend outgrows its initial assumptions.

In my case, that assumption was simple:
email = user identity

It worked fine early on. It was convenient, readable, and easy to query. But over time, it started to create real limitations—both in terms of features and security.

This post is about why I moved away from using email as the primary identifier, what had to change, and what improved as a result.


The Problem with Using Email as Identity

Initially, email was used everywhere:

  • in JWT subject
  • in database queries
  • across service layer logic

Typical example:

userRepository.findByEmail(email)
Enter fullscreen mode Exit fullscreen mode

And in JWT:

.setSubject(user.getEmail())
Enter fullscreen mode Exit fullscreen mode

This approach has one fundamental flaw:

Email is not a stable identifier.

The moment you want to support changing email, things start to break:

  • JWT tokens become inconsistent
  • existing sessions may become invalid
  • references across the system lose integrity

In practice, it blocks you from implementing a very basic feature: updating user email.


Why This Matters

Using a mutable field as identity introduces a few issues:

1. Tight coupling
Your entire system depends on a value that can change.

2. Inconsistent authentication
JWT subject no longer represents a stable user reference.

3. Limited extensibility
Features like:

  • email change
  • social login
  • account linking become harder to implement cleanly

The Refactor: Switching to User ID

The core idea was straightforward:

Use userId as the single source of identity across the system.

Email is now just a property of the user—not something the system relies on for identification.


Key Changes

1. JWT now stores user ID

.setSubject(user.getId().toString())
.claim("userId", user.getId())
Enter fullscreen mode Exit fullscreen mode

This ensures:

  • the identifier is stable
  • it maps directly to the database
  • it doesn’t change over time

2. Extracting user ID from token

public Long extractUserId(String token) {
    Claims claims = extractAllClaims(token);

    Object userIdClaim = claims.get("userId");
    if (userIdClaim instanceof Number number) {
        return number.longValue();
    }
    if (userIdClaim instanceof String userIdText) {
        return Long.parseLong(userIdText);
    }

    return Long.parseLong(claims.getSubject());
}
Enter fullscreen mode Exit fullscreen mode

This handles multiple formats and keeps backward compatibility with older tokens.


3. Authentication filter now works with ID

Before:

findByEmail(...)
Enter fullscreen mode Exit fullscreen mode

After:

Long userId = jwtService.extractUserId(jwt);
User user = userRepository.findById(userId).orElse(null);
Enter fullscreen mode Exit fullscreen mode

This removes ambiguity and avoids relying on user-controlled fields.


4. CustomUserDetails updated

private final Long id;
private final String email;
Enter fullscreen mode Exit fullscreen mode

The important part is that ID is now always available in the security context.


5. Centralized access via SecurityUtils

public static Long getCurrentUserId() {
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

    if (authentication == null || !authentication.isAuthenticated()) {
        throw new IllegalStateException("No authenticated user in security context");
    }

    Object principal = authentication.getPrincipal();

    if (principal instanceof CustomUserDetails userDetails) {
        return userDetails.getId();
    }

    if (principal instanceof String principalValue) {
        return Long.parseLong(principalValue);
    }

    throw new IllegalStateException("Unsupported principal type");
}
Enter fullscreen mode Exit fullscreen mode

This becomes the standard way to access the current user across the application.


What Improved

More predictable authentication

User identity is now:

  • immutable
  • consistent across requests
  • independent of user input

Better security model

The system no longer relies on fields that users can change.
Every request resolves to a database ID, which must exist.


Easier feature development

Adding support for:

  • email change
  • multiple authentication methods
  • external providers

is now straightforward.


What Was Costly

This wasn’t a small refactor.

It required:

  • updating JWT generation and parsing
  • rewriting authentication filter logic
  • replacing findByEmail across the codebase
  • adjusting services and flows that depended on email

It’s the kind of change that touches a lot of code and needs to be done consistently.


Things to Watch Out For

If you’re considering a similar change:

Old tokens
Tokens using email may stop working. You may need to force re-authentication.

Partial refactors
Mixing email-based and ID-based logic will lead to subtle bugs.

Tests
Without proper test coverage, it’s easy to introduce regressions in authentication flow.


Thanks for reading!

If you'd like to see Finovara development, check out my GitHub!

GitHub logo M4rc1nek / finovara-backend

Backend service for a personal finance management application

Finovara

Finovara is a financial management platform designed to help users effectively track analyze, and optimize their income, expenses, and savings The application provides a secure, bank-like experience focused on transparency, financial awareness, and long-term money planning.

🎯 Purpose of the Application

Finovara aims to support users in making better financial decisions by offering clear insights into their financial activity and helping them maintain control over their budgets and savings.

The platform focuses on:

  • organizing income and expenses in a structured way
  • visualizing financial data through charts and statistics
  • supporting saving goals and spending limits
  • providing a virtual wallet concept for daily financial management

🚀 Key Features

  • Secure user authentication and authorization
  • Income and expense tracking
  • Categorization of financial operations
  • Interactive charts and financial statistics
  • Reports summarizing spending and income trends
  • Virtual wallet management
  • Savings goals (e.g. piggy banks)
  • Spending limits and budget control
  • Scalable architecture prepared for future financial…

Top comments (0)