Skip to content

How to Handle User Relationships with Auth0 in Spring Boot

Problem

I started using Auth0 for authentication, but I hit a wall. Auth0 manages users externally, but my application needs to reference users in my database. How do I create foreign key relationships? How do I query “all orders for user X”? How do I store additional user data that Auth0 doesn’t manage?

What Auth0 Stores vs What You Need

Data ownership diagram
Auth0 User Store (External):
├── Email
├── Password (hashed)
├── Profile (name, picture)
├── MFA settings
├── Social login connections
└── Login history
Your Database (Local):
├── Application roles
├── User preferences
├── Orders, posts, comments (FK to user)
├── Business logic data
└── Audit trail

Auth0’s user store is external and opaque. You can’t add foreign keys to it, can’t join against it in SQL, and can’t extend it with application-specific fields.

The Solution: Proxy User Entity

The pattern is called “Proxy User Entity”—create a minimal local User entity that stores the Auth0 user ID (sub claim) as a reference.

Step 1: Create local User entity

User.java
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "auth0_id", unique = true, nullable = false)
private String auth0Id; // JWT "sub" claim
@Column(name = "email")
private String email;
@Column(name = "created_at")
private LocalDateTime createdAt;
// Application-specific fields
@Column(name = "preferences")
@Convert(converter = JsonConverter.class)
private Map<String, Object> preferences;
// Relationships to other entities
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Order> orders;
@OneToMany(mappedBy = "author")
private List<Post> posts;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
}
// Getters and setters...
}

Step 2: Create related entities

Order.java
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;
@Column(name = "total")
private BigDecimal total;
@Column(name = "created_at")
private LocalDateTime createdAt;
// Other order fields...
}
Post.java
@Entity
@Table(name = "posts")
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "author_id", nullable = false)
private User author;
@Column(name = "title")
private String title;
@Column(name = "content", columnDefinition = "TEXT")
private String content;
@Column(name = "created_at")
private LocalDateTime createdAt;
}

Step 3: Resolve users from JWT

UserService.java
@Service
public class UserService {
private final UserRepository userRepository;
public User getOrCreateUser(String auth0Id, String email) {
return userRepository.findByAuth0Id(auth0Id)
.orElseGet(() -> {
User newUser = new User();
newUser.setAuth0Id(auth0Id);
newUser.setEmail(email);
newUser.setCreatedAt(LocalDateTime.now());
return userRepository.save(newUser);
});
}
public User getCurrentUser(Authentication authentication) {
String auth0Id = authentication.getName();
return userRepository.findByAuth0Id(auth0Id)
.orElseThrow(() -> new UserNotFoundException(auth0Id));
}
}

Step 4: Use in controllers

OrderController.java
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final OrderService orderService;
private final UserService userService;
@PostMapping
public Order createOrder(@RequestBody CreateOrderRequest request,
Authentication authentication) {
User currentUser = userService.getCurrentUser(authentication);
return orderService.createOrder(currentUser, request);
}
@GetMapping
public List<Order> getMyOrders(Authentication authentication) {
User currentUser = userService.getCurrentUser(authentication);
return orderService.getOrdersByUser(currentUser.getId());
}
}

Why This Pattern Works

Benefits:

BenefitDescription
Relational integrityForeign keys ensure data consistency
Query performanceIndex and join on local tables, not external API calls
FlexibilityAdd application-specific fields to User entity
DecouplingAuth0 outage doesn’t prevent basic app functionality
TestingMock User entities easily in unit tests

Database Schema

migration.sql
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
auth0_id VARCHAR(255) UNIQUE NOT NULL,
email VARCHAR(255),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
preferences JSONB
);
CREATE INDEX idx_users_auth0_id ON users(auth0_id);
CREATE INDEX idx_users_email ON users(email);
CREATE TABLE orders (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id),
total DECIMAL(10, 2) NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE TABLE posts (
id BIGSERIAL PRIMARY KEY,
author_id BIGINT NOT NULL REFERENCES users(id),
title VARCHAR(255) NOT NULL,
content TEXT,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_posts_author_id ON posts(author_id);

What I Got Wrong

Mistake 1: Calling Auth0 Management API for every request

Wrong approach
// WRONG: External API call on every request
public User getUser(String auth0Id) {
return auth0ManagementAPI.getUser(auth0Id); // Slow, rate-limited
}
// CORRECT: Use local database
public User getUser(String auth0Id) {
return userRepository.findByAuth0Id(auth0Id);
}

Mistake 2: Not handling “user not found”

Wrong approach
// WRONG: Assume user exists
User user = userRepository.findByAuth0Id(auth0Id).get(); // May throw
// CORRECT: Handle missing users
User user = userRepository.findByAuth0Id(auth0Id)
.orElseGet(() -> createNewUser(auth0Id, email));

Mistake 3: Duplicating all user data

Store minimal data locally:

  • auth0Id (required for FK reference)
  • email (for convenience)
  • createdAt (for audit)

Let Auth0 manage profile data (name, picture, etc.). Sync only what you need.

Mistake 4: Using email as foreign key reference

Wrong approach
// WRONG: Email can change in Auth0
@Column(name = "email", unique = true)
@Id
private String email;
// CORRECT: Use immutable Auth0 ID
@Column(name = "auth0_id", unique = true)
private String auth0Id;

The Auth0 ID (sub claim) is immutable and unique. Email can change.

User Repository

UserRepository.java
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByAuth0Id(String auth0Id);
Optional<User> findByEmail(String email);
boolean existsByAuth0Id(String auth0Id);
}

Automatic User Creation

Create users automatically on first request:

UserResolutionFilter.java
@Component
public class UserResolutionFilter extends OncePerRequestFilter {
private final UserService userService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.isAuthenticated()) {
String auth0Id = auth.getName();
String email = extractEmailFromAuth(auth);
userService.getOrCreateUser(auth0Id, email);
}
filterChain.doFilter(request, response);
}
private String extractEmailFromAuth(Authentication auth) {
if (auth.getPrincipal() instanceof Jwt jwt) {
return jwt.getClaimAsString("email");
}
return null;
}
}

Best Practices

  1. Store the JWT sub claim as unique identifier
  2. Create users lazily—when JWT is first seen
  3. Sync email on each login—keep email current from Auth0
  4. Use Auth0 Management API sparingly—only for admin operations
  5. Index the auth0Id column—for fast lookups
  6. Handle user deletion—soft delete or cascade appropriately

Summary

In this post, I explained how to handle user relationships when using Auth0 in Spring Boot. The key point is creating a proxy User entity that stores the Auth0 sub claim as a unique reference, then establishing relationships between your entities and this local User entity. This gives you Auth0’s security benefits while maintaining full relational database capabilities for your application data.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments