Spring Boot GraphQL JWE Authentication combines the flexibility of GraphQL with stateless encrypted JWTs (JWE) and a JPA-backed user store to deliver a secure, scalable API.
Written by
Şuayb Şimşek
Backend-focused fullstack developer sharing practical notes on Spring Boot, security, microservices, and cloud-native architecture.
In this section, we define all of the application- and database-level configuration files required to wire up our Spring Boot app with H2/PostgreSQL, JPA, Liquibase changelogs, initial data loads, JWE key properties and GraphQL schema definitions, and GraalVM native-image reflection config.
application.yml
Holds Spring datasource, H2 console, JPA/Hibernate settings, Liquibase changelog path, GraphQL subscriptions path, and all JWT/JWE keys, issuer, and expiration configurations.
liquibase/master.xml
The Liquibase master changelog, with includes and DBMS-specific properties for H2 and PostgreSQL.
liquibase/changelog/changelog-user.xml
Your core schema changelog defining user_identity, authority, and user_authority_mapping tables, indexes, FKs, and initial <loadData> steps.
liquibase/data/user.csv
Initial user records (UUID, username, bcrypt-hashed password, email, enabled flag, timestamps, auditor).
liquibase/data/authority.csv
Initial authority records (UUID, name, description, timestamps, auditor).
graphql/schema.graphqls
GraphQL schema definitions for the API, including custom scalars, query, subscription and mutation types, and DTO definitions.
META-INF/native-image/liquibase/reflect-config.json
Native-image reflection configuration for Liquibase classes to ensure compatibility when building a GraalVM native image.
In this section, we define the beans and properties RSA keys, HTTP security filters, and JPA repository/auditing setup for JWE‑based authentication, GraalVM native-image runtime hints, GraphQL wiring and custom scalars:
JwtProperties: Configures JWT issuer, expiration, and signing/encryption key pairs.
SecurityJwtConfig: Builds RSA keys and JWK sources; configures JWT encoder/decoder, authentication converter, token resolver, and WebSocket interceptor beans.
SecurityConfig: Integrates DomainUserDetailsService, configures authentication manager, password encoder, and stateless security filter chain with JWE support.
DatabaseConfig: Enables JPA repositories, auditing, and transaction management.
GraphQLConfig: Registers custom scalars for GraphQL (Long, Date, Instant).
InstantScalar: Defines an ISO-8601 Instant scalar for GraphQL.
NativeConfig: Registers runtime hints for GraalVM native-image, including reflection and resource patterns.
SecurityJwtConfig
SecurityConfig
JwtProperties
DatabaseConfig
GraphQLConfig
InstantScalar
NativeConfig
Main
🛠️ Step 4: JPA Integration
In this section, we define the JPA entities representing users, authorities, and their mappings, along with the Spring Data repository for loading users with their authorities.
Authority: authority table entity storing role data.
User: user_identity table entity storing user credentials and profile.
UserAuthorityMapping: user_authority_mapping join table entity linking users and authorities.
UserAuthorityMappingId: Composite key class for UserAuthorityMapping.
UserRepository: Spring Data JPA repository for User with an entity graph to load authorities.
BaseEntity
Authority
User
UserAuthorityMapping
UserAuthorityMappingId
UserRepository
🛠️ Step 5: Secure JWE Token Utilities
In this section, we define the core utility classes and constants needed to generate, encrypt, and resolve JSON Web Encryption (JWE) tokens in your Spring Boot application, integrate auditing, and implement a JPA-based user details service:
AuthoritiesConstants: Centralize role names with the ROLE_ prefix.
CookieBearerTokenResolver: Resolve bearer tokens from Authorization headers or HTTP cookies.
CookieUtils: Generates HTTP-only, secure ResponseCookie for new or expired tokens, and extracts the raw accessToken from HttpHeaders
JweUtil: Sign (JWS) and encrypt (JWE) JWTs using RSA keys and Nimbus.
KeyUtils: Build RSA JWKs from PEM‐encoded key material.
SecurityUtils: Extract the current user’s login from the security context.
SpringSecurityAuditorAware: Implement AuditorAware to provide the current user for auditing.
DomainUserDetailsService: JPA-based UserDetailsService loading user and authorities for authentication.
GraphQlTokenCookieInterceptor: Intercepts GraphQL responses to set or clear the access token cookie based on accessToken and clearAccessToken flags in the GraphQLContext.
CookieAuthenticationWebSocketInterceptor: Intercept WebSocket connections to authenticate using JWE tokens from headers or cookies.
These utilities form the foundation for a stateless, JWE‐based authentication flow in Spring Security.
AuthoritiesConstants
CookieBearerTokenResolver
CookieUtils
JweUtil
KeyUtils
SecurityUtils
DomainUserDetailsService
SpringSecurityAuditorAware
GraphQlTokenCookieInterceptor
CookieAuthenticationWebSocketInterceptor
🛠️ Step 6: Authentication & Protected Endpoints
In this section, we define the GraphQL controllers and DTOs necessary for:
AuthController: Authenticates users, issues JWE tokens via a GraphQL mutation, and places accessToken or clearAccessToken flags into the GraphQLContext.
HelloController: Expose protected GraphQL queries, subscriptions and mutations for authenticated users and admin-only operations.
LoginInput: GraphQL input type for login (username/password).
GreetInput & GreetDTO: GraphQL mutation input and response for a greeting operation.
TokenDTO: Model the JWE token response including token, type, and expiration.
These components complete the stateless authentication flow in a GraphQL API using JWE tokens and a JPA-backed user store.
AuthController
HelloController
LoginInput
GreetInput
GreetDTO
TokenDTO
▶️ Run the App
BASH
./mvnw spring-boot:run
# or
gradle bootRun
If you have GraalVM 22.3+ installed, you can compile a native image with the native profile:
BASH
./mvnw native:compile -Pnative
After successful native-image compilation, the executable will be generated under target/ (e.g., target/spring-boot-graphql-jwe-auth-demo). Run it directly:
BASH
./target/spring-boot-graphql-jwe-auth-demo
Optionally, compress the native executable using UPX for a smaller file size (if UPX is installed):
Click ▶️. Only tokens with ROLE_ADMIN receive data; others see an authorization error.
🏁 Conclusion
You now have a practical Spring Boot GraphQL JWE Authentication implementation with a clear, production-friendly Spring Boot structure. As a next step, adapt configuration and tests to your own domain, then validate behavior under realistic traffic and failure scenarios.
JAVASecurityJwtConfig.java
package io.github.susimsek.springbootgraphqljwedemo.config;
import com.nimbusds.jose.EncryptionMethod;
import com.nimbusds.jose.JWEAlgorithm;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.JWEDecryptionKeySelector;
import com.nimbusds.jose.proc.JWSVerificationKeySelector;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
import io.github.susimsek.springbootgraphqljwedemo.security.CookieAuthenticationWebSocketInterceptor;
import io.github.susimsek.springbootgraphqljwedemo.security.CookieBearerTokenResolver;
import io.github.susimsek.springbootgraphqljwedemo.security.KeyUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.graphql.server.WebSocketGraphQlInterceptor;
import org.springframework.graphql.server.support.BearerTokenAuthenticationExtractor;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import java.util.List;
import static io.github.susimsek.springbootgraphqljwedemo.security.SecurityUtils.AUTHORITIES_KEY;
@Configuration
public class SecurityJwtConfig {
private final JwtProperties props;
public SecurityJwtConfig(JwtProperties props) {
this.props = props;
}
@Bean
public RSAKey signingKey() throws Exception {
return KeyUtils.buildRsaKey(
props.getSigning().getPublicKey(),
props.getSigning().getPrivateKey(),
props.getSigning().getKeyId(),
true
);
}
@Bean
public RSAKey encryptionKey() throws Exception {
return KeyUtils.buildRsaKey(
props.getEncryption().getPublicKey(),
props.getEncryption().getPrivateKey(),
props.getEncryption().getKeyId(),
false
);
}
@Bean
public JWKSource<SecurityContext> jwkSource(RSAKey signingKey, RSAKey encryptionKey) {
JWKSet jwkSet = new JWKSet(List.of(signingKey, encryptionKey));
return (jwkSelector, context) -> jwkSelector.select(jwkSet);
}
@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
DefaultJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
jwtProcessor.setJWEKeySelector(new JWEDecryptionKeySelector<>(
JWEAlgorithm.RSA_OAEP_256,
EncryptionMethod.A128GCM,
jwkSource
));
jwtProcessor.setJWSKeySelector(new JWSVerificationKeySelector<>(
JWSAlgorithm.RS256,
jwkSource
));
jwtProcessor.setJWTClaimsSetVerifier((claims, ctx) -> {});
return new NimbusJwtDecoder(jwtProcessor);
}
@Bean
public JwtEncoder jwtEncoder(JWKSource<SecurityContext> jwkSource) {
return new NimbusJwtEncoder(jwkSource);
}
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter converter = new JwtGrantedAuthoritiesConverter();
converter.setAuthorityPrefix("");
converter.setAuthoritiesClaimName(AUTHORITIES_KEY);
JwtAuthenticationConverter authConverter = new JwtAuthenticationConverter();
authConverter.setJwtGrantedAuthoritiesConverter(converter);
return authConverter;
}
@Bean
public BearerTokenResolver bearerTokenResolver() {
CookieBearerTokenResolver resolver = new CookieBearerTokenResolver();
resolver.setAllowUriQueryParameter(false);
resolver.setAllowFormEncodedBodyParameter(false);
resolver.setAllowCookie(true);
return resolver;
}
@Bean
public WebSocketGraphQlInterceptor authenticationInterceptor(JwtDecoder jwtDecoder) {
return new CookieAuthenticationWebSocketInterceptor(
new BearerTokenAuthenticationExtractor(),
new ProviderManager(new JwtAuthenticationProvider(jwtDecoder))
);
}
}
package io.github.susimsek.springbootgraphqljwedemo.security;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class AuthoritiesConstants {
public static final String ADMIN = "ROLE_ADMIN";
public static final String USER = "ROLE_USER";
public static final String ANONYMOUS = "ROLE_ANONYMOUS";
}
KOTLINAuthoritiesConstants.kt
package io.github.susimsek.springbootgraphqljwedemo.security
object AuthoritiesConstants {
const val ADMIN = "ROLE_ADMIN"
const val USER = "ROLE_USER"
const val ANONYMOUS = "ROLE_ANONYMOUS"
}
package io.github.susimsek.springbootgraphqljwedemo.dto;
public record LoginInput(
String username,
String password
) {}
KOTLINLoginInput.kt
package io.github.susimsek.springbootgraphqljwedemo.dto
data class LoginInput(
val username: String,
val password: String
)
JAVAGreetInput.java
package io.github.susimsek.springbootgraphqljwedemo.dto;
public record GreetInput(
String message
) {}
KOTLINGreetInput.kt
package io.github.susimsek.springbootgraphqljwedemo.dto
data class GreetInput(
val message: String
)
JAVAGreetDTO.java
package io.github.susimsek.springbootgraphqljwedemo.dto;
import java.time.Instant;
public record GreetDTO(
String greeting,
Instant timestamp
) {}
KOTLINGreetDTO.kt
package io.github.susimsek.springbootgraphqljwedemo.dto
import java.time.Instant
data class GreetDTO(
val greeting: String,
val timestamp: Instant
)
JAVATokenDTO.java
package io.github.susimsek.springbootgraphqljwedemo.dto;
public record TokenDTO(
String accessToken,
String tokenType,
long accessTokenExpiresIn
) {}
KOTLINTokenDTO.kt
package io.github.susimsek.springbootgraphqljwedemo.dto
data class TokenDTO(
val accessToken: String,
val tokenType: String,
val accessTokenExpiresIn: Long
)