Implement User Impersonation in Spring WebFlux with JWT Authentication

Implement User Impersonation in Spring WebFlux with JWT Authentication

Published: March 22, 2026  |  By Engineer’s CodingHub  |  Spring WebFlux, JWT, Security

Overview

In this article, we will implement a complete user impersonation feature using Spring WebFlux and JWT-based authentication, and understand the full flow step by step.

Implement User Impersonation in Spring WebFlux with JWT Authentication

What Is User Impersonation?

User impersonation is a powerful feature commonly used in admin panels, support dashboards, and SaaS applications. It allows an admin to temporarily act as another user in the system without knowing that user’s credentials. This is extremely useful for debugging, customer support, and resolving user-specific issues quickly.

User Impersonation Means:

  • An admin logs in using their own credentials
  • The admin selects a user from a list
  • The system switches identity and behaves as that user
  • The admin can later exit impersonation and return to their original account

The key challenge is implementing this securely in a stateless JWT-based system.

Why Impersonation Is Tricky with JWT

JWT authentication is stateless, which means:

  • No server-side session is stored.
  • Every request is authenticated using the token alone.

Spring Security’s SwitchUserWebFilter is traditionally session-based. To make impersonation work with JWT, we use the following approach:

We generate a new JWT for the impersonated user and store the original admin details inside the token’s claims. This keeps the system fully stateless while tracking impersonation context.

High-Level Design

  • Admin logs in and receives a JWT
  • Admin clicks “Impersonate” on a target user
  • Backend issues a new JWT for the selected user
  • The new token contains the original admin’s information as a claim
  • Frontend replaces the stored token with the impersonation token
  • All subsequent requests behave as the impersonated user
  • Admin exits impersonation and receives a fresh admin JWT

Roles Used in the System

  • ROLE_ADMIN — Can perform impersonation
  • ROLE_USER — Standard user, can be impersonated

Only users with ROLE_ADMIN are allowed to impersonate other users.

JWT Token Structure

Normal Admin JWT


{
  "sub": "admin@gmail.com",
  "roles": ["ROLE_ADMIN"],
  "impersonated": false
}

Impersonated User JWT


{
  "sub": "user1@gmail.com",
  "roles": ["ROLE_USER"],
  "impersonated": true,
  "originalAdmin": "admin@gmail.com"
}

These additional claims allow us to track impersonation without breaking stateless authentication.

Spring WebFlux Security Configuration


@EnableWebFluxSecurity
public class SecurityConfig {

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(
            ServerHttpSecurity http,
            SwitchUserWebFilter switchUserWebFilter) {

        return http
                .csrf(ServerHttpSecurity.CsrfSpec::disable)
                .authorizeExchange(exchanges -> exchanges
                        .pathMatchers("/auth/**").permitAll()
                        .pathMatchers("/admin/**").hasRole("ADMIN")
                        .anyExchange().authenticated()
                )
                .addFilterAfter(switchUserWebFilter, SecurityWebFiltersOrder.AUTHORIZATION)
                .build();
    }
}

SwitchUserWebFilter Configuration

Even though JWT is stateless, we still configure SwitchUserWebFilter to define the impersonation endpoints. The actual identity switching is handled by issuing new JWTs.


@Bean
public SwitchUserWebFilter switchUserWebFilter(
        ReactiveUserDetailsService userDetailsService) {

    SwitchUserWebFilter filter = new SwitchUserWebFilter();
    filter.setUserDetailsService(userDetailsService);
    filter.setSwitchUserUrl("/admin/impersonate");
    filter.setExitUserUrl("/admin/exit-impersonation");
    filter.setTargetUrl("/");

    return filter;
}

Impersonation API Endpoint

This endpoint is called when an admin clicks the “Impersonate” button for a specific user:


POST /admin/impersonate/{userEmail}

Impersonation Controller


@RestController
@RequestMapping("/admin")
public class ImpersonationController {

    private final UserService userService;
    private final JwtService jwtService;

    public ImpersonationController(
            UserService userService,
            JwtService jwtService) {
        this.userService = userService;
        this.jwtService = jwtService;
    }

    @PostMapping("/impersonate/{email}")
    public Mono<ResponseEntity<Map<String, String>>> impersonateUser(
            @PathVariable String email,
            Authentication authentication) {

        String adminUsername = authentication.getName();

        return userService.findByEmail(email)
                .map(user -> {
                    String token =
                            jwtService.generateImpersonationToken(
                                    user.getEmail(),
                                    user.getRoles(),
                                    adminUsername
                            );

                    Map<String, String> response = new HashMap<>();
                    response.put("token", token);
                    response.put("impersonatedUser", user.getEmail());

                    return ResponseEntity.ok(response);
                });
    }
}

JWT Generation for Impersonation


public String generateImpersonationToken(
        String username,
        List<String> roles,
        String adminUsername) {

    Map<String, Object> claims = new HashMap<>();
    claims.put("roles", roles);
    claims.put("impersonated", true);
    claims.put("originalAdmin", adminUsername);

    return Jwts.builder()
            .setSubject(username)
            .setClaims(claims)
            .setIssuedAt(new Date())
            .setExpiration(
                    new Date(System.currentTimeMillis() + 60 * 60 * 1000)
            )
            .signWith(secretKey)
            .compact();
}

JWT Authentication Handling in WebFlux

Your existing JWT authentication filter should extract these claims from every incoming token:

  • subject — the current user’s email
  • roles — the current user’s roles
  • impersonated — whether this is an impersonation token
  • originalAdmin — the admin who initiated impersonation

These values are then used to build the Authentication object:


String username = claims.getSubject();

Boolean impersonated =
        claims.get("impersonated", Boolean.class);

String originalAdmin =
        claims.get("originalAdmin", String.class);

Authorization Behavior During Impersonation

Once impersonation is active:

  • The admin no longer has admin-level permissions
  • Only user-level APIs are accessible
  • All existing security rules remain fully in effect

This happens naturally because the impersonated JWT only contains ROLE_USER — no changes to the security layer are required.

Exit Impersonation API

To exit impersonation and restore admin access, call:


POST /admin/exit-impersonation

Exit Impersonation Controller


@PostMapping("/exit-impersonation")
public Mono<ResponseEntity<Map<String, String>>> exitImpersonation(
        Authentication authentication) {

    Jwt jwt = (Jwt) authentication.getPrincipal();

    String originalAdmin =
            jwt.getClaim("originalAdmin");

    String adminToken =
            jwtService.generateAdminToken(originalAdmin);

    Map<String, String> response = new HashMap<>();
    response.put("token", adminToken);

    return Mono.just(ResponseEntity.ok(response));
}

Complete Flow Summary

Step 1 — Admin Login

  • Admin logs in with their credentials
  • Receives a JWT containing ROLE_ADMIN

Step 2 — Impersonation

  • Admin selects a target user
  • Backend generates an impersonated JWT
  • Frontend replaces the stored token
  • All subsequent requests act as the impersonated user

Step 3 — Exit Impersonation

  • Admin exits impersonation
  • Backend reads originalAdmin claim and issues a fresh admin JWT
  • Frontend replaces the token
  • Full admin access is restored

Frontend Responsibilities

  • Replace the JWT on impersonation immediately
  • Show a clear visual banner indicating that impersonation is active
  • Call the exit endpoint when the admin wants to return
  • Never store an impersonated token in long-term storage

Security Best Practices

  • Restrict impersonation endpoints to ROLE_ADMIN only
  • Log every impersonation action with timestamp and admin identity
  • Use short expiry times for impersonated tokens (e.g., 1 hour)
  • Prevent nested impersonation (an already-impersonating admin cannot re-impersonate)
  • Display impersonation status clearly in the UI

Conclusion

User impersonation in Spring WebFlux with JWT is fully achievable without breaking stateless authentication. By issuing new JWTs and embedding impersonation metadata in the token claims, you get a secure, scalable, and production-ready solution that works seamlessly with any reactive Spring application.

This pattern is widely used in real-world SaaS platforms and admin dashboards where customer support teams need to reproduce user issues quickly and safely.

If you found this guide helpful, check out our related articles on JWT Authentication with Spring Boot and Spring WebFlux security patterns.

Engineer’s CodingHub

A platform dedicated to helping developers master Spring Boot, Spring WebFlux, Java, and modern backend engineering through in-depth tutorials and real-world examples.

View all articles →

3 thoughts on “Implement User Impersonation in Spring WebFlux with JWT Authentication”

Leave a Comment