skip to content

How does a Spring RSocket requester present authentication metadata (simple username/password vs bearer/JWT), and how does the server validate it?

level: seniorimportance: should knowfreq 30%

answer

  1. WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION
  2. UsernamePasswordMetadata (simple) vs BearerTokenMetadata (jwt)
  3. setupMetadata(...) vs .metadata(creds, mime) per request
  4. server: simpleAuthentication() vs jwt(ReactiveJwtDecoder)
  5. always over TLS/WSS — metadata isn't encrypted by itself

basics

~20 s

The requester attaches auth metadata using a well-known RSocket mime type. For simple auth it sends a UsernamePasswordMetadata; for bearer it sends a BearerTokenMetadata (a JWT). The server enables simpleAuthentication() or jwt() on RSocketSecurity to decode and validate it.

solid answer

~40 s

RSocket carries authentication in composite metadata under standardized mime types. For username/password, Spring uses the mime type message/x.rsocket.authentication.v0 (WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION) and a UsernamePasswordMetadata payload; for a token you use BearerTokenMetadata with the same authentication mime type. On the requester you attach it via setupMetadata(...) for connection-level auth or .metadata(credentials, mimeType) per request. On the server, RSocketSecurity.simpleAuthentication(...) registers a decoder plus a ReactiveAuthenticationManager (backed by a UserDetailsService/PasswordEncoder), while jwt(...) wires a JWT decoder (ReactiveJwtDecoder) and converts claims to authorities. Spring's PayloadSocketAcceptorInterceptor extracts the metadata, produces an Authentication, and stores it in the reactive security context so authorizePayload rules and @MessageMapping handlers see the principal. Simple auth suits internal user credentials; bearer/JWT suits token-based, OAuth2-style trust.

code

java · 24 lines
java
// --- Server ---
@Bean
PayloadSocketAcceptorInterceptor rsocketInterceptor(RSocketSecurity security) {
    return security
        .authorizePayload(a -> a.anyRequest().authenticated().anyExchange().permitAll())
        .jwt(jwt -> jwt.authenticationManager(jwtAuthManager()))
        .build();
}

ReactiveAuthenticationManager jwtAuthManager() {
    ReactiveJwtDecoder decoder = NimbusReactiveJwtDecoder
            .withJwkSetUri("https://issuer/.well-known/jwks.json").build();
    return new JwtReactiveAuthenticationManager(decoder);
}

// --- Requester (bearer) ---
BearerTokenMetadata token = new BearerTokenMetadata(jwtString);
MimeType authMime = MimeTypeUtils.parseMimeType(
        WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString());
Mono<String> reply = requester
        .route("secure.echo")
        .metadata(token, authMime)
        .data("hi")
        .retrieveMono(String.class);

go deeper

for a junior

Know the requester attaches credentials as metadata and the server enables simpleAuthentication or jwt.

for a middle

Name the mime type and metadata classes (UsernamePasswordMetadata/BearerTokenMetadata) and attach them at setup or per request.

for a senior

Wire the full flow: encoders on requester, ReactiveAuthenticationManager/ReactiveJwtDecoder on server, principal in reactive context, TLS requirement.

for a principal

Choose simple vs bearer per trust model, integrate with OAuth2/OIDC, reason about JWKS caching, per-frame validation cost, and credential exposure.

RSocket doesn't define auth itself — it defines a **metadata** mechanism. A frame's metadata can be a single blob or **composite metadata**: multiple entries, each tagged with a mime type. Spring Security defines/uses **well-known mime types** to carry credentials: - **`message/x.rsocket.authentication.v0`** — `WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION`. A generic authentication metadata slot that can hold either simple or bearer credentials (encoded differently). - Routing uses **`message/x.rsocket.routing.v0`** (`MESSAGE_RSOCKET_ROUTING`) to select the `@MessageMapping` route — separate from auth but usually sent alongside in composite metadata. **Requester side — simple (username/password):** ```java UsernamePasswordMetadata creds = new UsernamePasswordMetadata("user", "pass"); MimeType authMime = MimeTypeUtils.parseMimeType( WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString()); RSocketRequester requester = builder .setupMetadata(creds, authMime) // connection-level auth .tcp("localhost", 7000); // or per request: requester.route("secure.echo").metadata(creds, authMime).data(msg)... ``` You also register a `SimpleAuthenticationEncoder` (or the RSocket strategies) so the requester knows how to serialize `UsernamePasswordMetadata`. **Requester side — bearer (JWT/OAuth2):** ```java BearerTokenMetadata token = new BearerTokenMetadata(jwtString); MimeType authMime = MimeTypeUtils.parseMimeType( WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString()); requester.route("secure.echo").metadata(token, authMime)... ``` with a `BearerTokenAuthenticationEncoder` registered. **Server side — enabling decoders/validators via `RSocketSecurity`:** - `simpleAuthentication(Customizer.withDefaults())` — installs the simple-auth payload interceptor. It needs a `ReactiveAuthenticationManager`; by default it uses a `UserDetailsRepositoryReactiveAuthenticationManager` built from your `MapReactiveUserDetailsService` + `PasswordEncoder`. - `jwt(jwt -> jwt.authenticationManager(...))` — installs JWT validation. Supply a `ReactiveJwtDecoder` (e.g. from `NimbusReactiveJwtDecoder.withJwkSetUri(...)` or a public key) and optionally a converter to map JWT scopes/claims to `GrantedAuthority`s (`JwtAuthenticationConverter` / `JwtReactiveAuthenticationManager`). - `basicAuthentication(...)` also exists but simple/bearer are the RSocket-native forms. The **`PayloadSocketAcceptorInterceptor`** reads the auth metadata via a **`PayloadExchangeAuthenticationConverter`** / metadata extractor, runs it through the authentication manager, and on success places the `Authentication` into the reactive `SecurityContext` (Reactor context). Then `authorizePayload` rules and handler-level checks apply. **Reading the principal in a handler:** ```java @MessageMapping("secure.echo") Mono<String> echo(@AuthenticationPrincipal Mono<UserDetails> user, String msg) { return user.map(u -> u.getUsername() + ": " + msg); } ``` **Gotchas / edge cases:** - **Encoders must be registered on the requester**, otherwise the metadata can't be serialized. Spring Boot auto-configures RSocket strategies but custom setups may need `SimpleAuthenticationEncoder`/`BearerTokenAuthenticationEncoder` added to `RSocketStrategies`. - Mixing up **routing** vs **authentication** mime types breaks either dispatch or auth silently. - **Setup auth vs request auth**: `setupMetadata(...)` authenticates the connection; `.metadata(...)` per call authenticates that request. They can coexist. - With JWT, the token is validated on **every** frame that carries it (or at setup) — ensure clocks/JWKS caching are configured to avoid latency. - Credentials in metadata are only as safe as the transport — always pair with TLS on TCP or WSS on WebSocket. - Simple auth transmits the password (encoded, not encrypted) — transport security is mandatory. **When to use which:** simple auth for internal, credential-based clients or dev; bearer/JWT when integrating with an OAuth2/OIDC ecosystem, when you want stateless, delegated trust, or when tokens already flow through your system.

  • Which mime type carries auth vs routing metadata, and why does the distinction matter?
    Auth uses message/x.rsocket.authentication.v0 (MESSAGE_RSOCKET_AUTHENTICATION); routing uses message/x.rsocket.routing.v0 (MESSAGE_RSOCKET_ROUTING). They're separate composite-metadata entries — swapping them breaks route dispatch or authentication with no obvious error.
  • Why must RSocket auth always run over TLS/WSS?
    Authentication metadata (especially simple username/password) is encoded, not encrypted — anyone sniffing plaintext TCP/WS can steal credentials or tokens. TLS/WSS provides the confidentiality RSocket metadata itself does not.
  • How do you map JWT claims to Spring authorities for RSocket authorization?
    Configure a converter (JwtAuthenticationConverter / JwtGrantedAuthoritiesConverter) on the JwtReactiveAuthenticationManager to translate scopes/claims into GrantedAuthoritys, which .route(...).hasRole/hasAuthority rules then evaluate.

saying these in an interview costs you the question

  • Claiming RSocket metadata is encrypted, so TLS is optional
  • Confusing the routing mime type with the authentication mime type
  • Thinking simple and bearer use entirely different mime types (both use MESSAGE_RSOCKET_AUTHENTICATION with different payload encodings)
  • Forgetting to register the Simple/Bearer authentication encoder on the requester

context