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
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 trailAuth0’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
@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
@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...}@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
@Servicepublic 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
@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:
| Benefit | Description |
|---|---|
| Relational integrity | Foreign keys ensure data consistency |
| Query performance | Index and join on local tables, not external API calls |
| Flexibility | Add application-specific fields to User entity |
| Decoupling | Auth0 outage doesn’t prevent basic app functionality |
| Testing | Mock User entities easily in unit tests |
Database Schema
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: External API call on every requestpublic User getUser(String auth0Id) { return auth0ManagementAPI.getUser(auth0Id); // Slow, rate-limited}
// CORRECT: Use local databasepublic User getUser(String auth0Id) { return userRepository.findByAuth0Id(auth0Id);}Mistake 2: Not handling “user not found”
// WRONG: Assume user existsUser user = userRepository.findByAuth0Id(auth0Id).get(); // May throw
// CORRECT: Handle missing usersUser 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: Email can change in Auth0@Column(name = "email", unique = true)@Idprivate 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
@Repositorypublic 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:
@Componentpublic 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
- Store the JWT
subclaim as unique identifier - Create users lazily—when JWT is first seen
- Sync email on each login—keep email current from Auth0
- Use Auth0 Management API sparingly—only for admin operations
- Index the auth0Id column—for fast lookups
- 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