Building authentication in Java applications: The complete guide for 2026
Master Spring Security authentication from form login and JWT to enterprise SSO, with production-ready patterns across Spring Boot, Quarkus, and Micronaut.
Authentication in Java has always been a first-class concern. The ecosystem offers more built-in security infrastructure than almost any other platform: Spring Security alone provides a comprehensive filter chain, password encoding, CSRF protection, session management, OAuth2 client and resource server support, and method-level authorization. Jakarta EE (formerly Java EE) defines standard security specifications. And newer frameworks like Quarkus and Micronaut ship with their own security modules designed for cloud-native deployments.
The challenge for Java developers is not a lack of tools. It is navigating the complexity of those tools and assembling them into a production-ready authentication system. Spring Security is powerful, but its filter chain architecture, multiple configuration styles, and extensive API surface can be overwhelming. This guide cuts through that complexity.
Whether you are building a REST API with Spring Boot, a microservice with Quarkus, or evaluating managed solutions, this guide covers the concepts, security patterns, and implementation approaches you need to make informed decisions for your Java application.
Understanding authentication in Java
Java approaches authentication through layered abstractions. Unlike Node.js (where you wire up middleware manually) or Laravel (where a single auth middleware handles most cases), Java frameworks provide deep, configurable security pipelines with multiple extension points.
The filter chain architecture
The filter chain is the foundational concept in Java web security. In the Servlet specification, filters intercept HTTP requests before they reach your controllers. Spring Security builds its entire authentication and authorization system on top of this mechanism.
When a request arrives at a Spring Boot application:
- The servlet container receives the request. Tomcat, Jetty, or Undertow (embedded in Spring Boot) accepts the incoming HTTP request.
- The
DelegatingFilterProxydelegates to Spring Security. Spring registers a single servlet filter that bridges the servlet container to Spring Security's internal filter chain. - Spring Security's
FilterChainexecutes. A sequence of security filters runs in a specific order. Each filter handles one concern: CSRF verification, session management, authentication, authorization, exception handling, and more. - Authentication filters verify the user. Depending on your configuration, this might be
UsernamePasswordAuthenticationFilter(for form login),BearerTokenAuthenticationFilter(for JWT/OAuth2), orBasicAuthenticationFilter(for HTTP Basic). - The
SecurityContextis populated. On successful authentication, Spring Security stores theAuthenticationobject in theSecurityContextHolder, making it available throughout the request. - Authorization filters check permissions. The
AuthorizationFilterevaluates whether the authenticated user has permission to access the requested resource, based on your security rules. - The request reaches your controller. Only if authentication and authorization both pass does the request arrive at your
@RestControlleror@Controllermethod.
Spring Security is a pipeline of 15 or more filters, each with a specific responsibility. Understanding this pipeline is essential for debugging authentication issues and customizing behavior.
Frameworks: Spring Boot, Quarkus, and Micronaut
Spring Boot with Spring Security is the dominant choice for Java authentication, used by the majority of enterprise Java applications. Spring Security provides the most comprehensive security framework in any language: authentication, authorization, OAuth2/OIDC client and resource server, SAML support, LDAP integration, method-level security, and protection against common attacks (CSRF, session fixation, clickjacking). The trade-off is complexity. Spring Security's flexibility means there are many ways to configure the same thing, and the documentation assumes familiarity with the filter chain model.
Quarkus takes a different approach. Its security module is built around a simpler model: annotate endpoints with @RolesAllowed, @Authenticated, or @PermitAll, and configure authentication mechanisms (Basic, form, JWT, OIDC) through application properties. Quarkus prioritizes fast startup and low memory usage, making it well suited for containerized and serverless deployments. Its OIDC extension provides a complete OAuth2/OIDC implementation. Session-based auth is more limited than Spring's; Quarkus is designed for stateless architectures.
Micronaut uses compile-time dependency injection and its own security module (micronaut-security). Authentication is handled through AuthenticationFetcher beans and SecurityRule beans, evaluated by a SecurityFilter on every request. Micronaut Security supports JWT, OAuth2/OIDC, Basic auth, session auth, and LDAP. Like Quarkus, it is optimized for cloud-native deployments with fast startup and low memory footprint.
The examples in this guide focus on Spring Boot with Spring Security, since it covers the widest range of use cases. Where the approaches differ meaningfully for Quarkus or Micronaut, those differences are noted.
Authentication implementation approaches
Java offers several well-supported paths for implementing authentication, from Spring Security's built-in mechanisms to cloud-native framework support and managed providers.
Approach 1: Spring Security with form login and sessions
This is the classic Spring Security approach for server-rendered web applications. Spring Boot auto-configures most of the setup; you customize it through a SecurityFilterChain bean.
With this configuration you get a login form with CSRF protection, session-based authentication with session fixation protection, bcrypt password hashing, automatic security headers, and logout with session invalidation. Spring Boot handles most of this out of the box; the SecurityFilterChain bean is where you customize the defaults.
What you still need to build yourself: user registration, email verification, password reset flows, MFA, and rate limiting on login attempts. This approach works best for server-rendered applications with Thymeleaf or JSP where authentication requirements are straightforward email and password login.
Approach 2: Spring Security with JWT for REST APIs
For stateless REST APIs, Spring Security can validate JWT tokens using its OAuth2 Resource Server support. This is the recommended approach for APIs consumed by SPAs, mobile apps, or other services.
For issuing tokens, create an authentication endpoint:
A few decisions matter here.
- For signing algorithms, use RS256 (asymmetric) if multiple services need to verify tokens independently, and HS256 (symmetric) for simpler single-service setups.
- Keep access tokens short-lived (up to 15 minutes tops) and use refresh tokens stored in
httpOnlycookies or a database for longer sessions.
Spring Security's oauth2-resource-server module handles JWT decoding, signature verification, claim validation, and mapping claims to GrantedAuthority objects automatically, so you don't need to write that plumbing yourself.
This approach fits REST APIs consumed by SPAs, mobile apps, or other services where you want stateless authentication that scales horizontally without shared session storage.
Approach 3: Spring Security with OAuth2/OIDC
Spring Security provides first-class support for acting as both an OAuth2 client (redirecting users to external providers like Google or GitHub) and an OAuth2 resource server (validating tokens from an authorization server).
Spring Boot auto-configures the OAuth2 client from these properties. For Google and GitHub, you don't even need to specify the authorization and token endpoints; Spring Security knows them by default. Out of the box you get the Authorization Code flow with PKCE, OpenID Connect discovery and ID token validation, automatic token refresh, and user info endpoint integration. It works with any OIDC-compliant provider.
Where Spring's OAuth2 support stops: enterprise SAML SSO (available through a separate Spring Security SAML extension but significantly more work to configure), SCIM directory sync, multi-tenant organization management, and admin portals for customer self-service configuration. If you need social login or want to integrate with an external identity provider, this is the right starting point. Spring handles the protocol complexity; you focus on mapping provider users to your application's user model.
Approach 4: Quarkus security
Quarkus takes a configuration-driven approach to authentication. Instead of programmatic filter chains, you declare authentication mechanisms and authorization rules through annotations and application properties.
For JWT authentication:
For OIDC (connecting to an external identity provider):
Quarkus shines in cloud-native environments: sub-second startup with native images, low memory usage, and strong OIDC support through the Quarkus OIDC extension. The annotation-driven model is simpler to reason about than Spring Security's filter chain, especially for teams that don't need fine-grained customization.
The trade-offs are real, though. Session-based auth is more limited than Spring's (Quarkus is stateless by design). The security ecosystem is smaller, so you'll find fewer community examples and third-party integrations. Advanced customization requires deeper knowledge of Quarkus's internal security architecture. This approach fits well when you're building microservices for Kubernetes, serverless platforms, or edge environments where startup time and memory footprint are priorities.
Approach 5: Micronaut security
Micronaut Security uses compile-time processing to wire authentication and authorization, eliminating the reflection-based overhead of Spring Security.
Micronaut occupies similar territory to Quarkus: cloud-native microservices, serverless, and scenarios where startup time and memory usage are critical. The key differentiator is that Micronaut's compile-time DI avoids reflection overhead entirely, which can matter for native image compilation and cold start performance. If you're already in the Micronaut ecosystem, its security module is capable and well integrated. If you're starting fresh, compare it against Quarkus and Spring Boot for your specific deployment target.
Approach 6: Managed authentication provider
The approaches above all run inside your infrastructure and require you to manage the authentication stack: user registration, password resets, email verification, MFA, session or token management, and ongoing security maintenance. Even with Spring Security's comprehensive feature set, building enterprise-ready authentication (SSO, directory sync, compliance) takes months of additional development.
A managed authentication provider handles all of this on external infrastructure and returns authenticated users to your application via a standard OAuth2/OIDC callback. You integrate with the provider the same way you would with any OAuth2 identity provider, using Spring Security's built-in OAuth2 support.
When evaluating providers, look for a dedicated Java SDK, compatibility with Spring Security's OAuth2 client, support for Maven and Gradle, a generous free tier, and a clear path from basic auth to enterprise features without requiring a rewrite.
WorkOS is a strong fit here. Its AuthKit product covers the full range of authentication needs (email/password, magic auth, social login, MFA, passkeys, enterprise SSO via SAML and OIDC, and directory sync via SCIM) in a single platform, with a free tier that supports up to 1 million monthly active users. It provides a Java SDK that integrates naturally with Spring Boot and other Java frameworks.
The integration follows the standard OAuth2 redirect-and-callback pattern:
Beyond basic authentication, WorkOS provides enterprise SSO (SAML and OIDC) without additional code, SCIM-based directory sync for automatic user provisioning, organization and team management with built-in multi-tenancy, audit logs, bot protection, and compliance features. These capabilities are available from day one and build on each other as your requirements grow.
This approach makes the most sense for B2B software where enterprise customers will eventually require SSO, directory sync, or compliance certifications. Rather than building those features over months and maintaining them indefinitely, you delegate them to a platform designed for that purpose and keep your team focused on your product.
Security considerations for Java authentication
Now that you have seen the implementation patterns, here is what can go wrong and what to watch for regardless of which approach you choose.
Deserialization vulnerabilities
Java's native serialization mechanism (ObjectInputStream) is one of the most dangerous features in the language. When an application deserializes untrusted data, an attacker can craft a malicious byte stream that executes arbitrary code on your server. This has been the root cause of some of the most severe Java vulnerabilities in history, including exploits targeting Apache Commons Collections, Spring Framework, and application servers.
In an authentication context, deserialization attacks can occur through session data stored in cookies, cached authentication tokens, or any endpoint that accepts serialized Java objects.
Defenses:
- Never deserialize untrusted Java objects. Use JSON for data interchange.
- If you must use Java serialization, use
ObjectInputFilter(introduced in Java 9) to whitelist allowed classes. - Be cautious with Jackson's polymorphic type handling (
@JsonTypeInfo,enableDefaultTyping). These features can reintroduce deserialization-style attacks through JSON. - Keep dependencies updated. Many deserialization exploits target specific library versions.
Dependency vulnerabilities
Java applications typically have deep dependency trees managed by Maven or Gradle. A single Spring Boot starter can pull in dozens of transitive dependencies, each a potential vector for vulnerabilities.
Defenses:
- Run
mvn dependency-check:check(OWASP Dependency-Check) orgradle dependencyCheckAnalyzein your CI pipeline. - Use Dependabot or Renovate to automate dependency updates.
- Review the Spring Security and Spring Boot release notes for security advisories.
- Pin dependency versions explicitly rather than relying on version ranges.
Password hashing
Spring Security supports multiple password encoding algorithms through its PasswordEncoder interface. The recommended default is bcrypt, which Spring Security uses via BCryptPasswordEncoder. Argon2 (Argon2PasswordEncoder) and PBKDF2 (Pbkdf2PasswordEncoder) are also available.
The DelegatingPasswordEncoder is particularly useful: it prefixes stored hashes with the algorithm identifier (e.g., {bcrypt}$2a$12$...), allowing you to migrate from one algorithm to another without forcing all users to reset their passwords.
Password best practices:
- Use
BCryptPasswordEncoderwith a strength of 12 or higher. Do not lower this in production. - Use
DelegatingPasswordEncoderif you anticipate switching algorithms in the future. - Require a minimum of 8 characters (12 or more is strongly recommended).
- Avoid strict complexity rules. Length is more effective than mandating special characters.
- Rate limit login attempts. Spring Security does not do this by default; you need to implement it yourself or use a library.
CSRF protection
Spring Security enables CSRF protection by default for all non-GET requests. This is appropriate for applications that use session-based authentication with cookies (Approach 1), since browsers automatically attach cookies to cross-origin requests.
For stateless REST APIs that use JWT in the Authorization header (Approach 2), CSRF protection is typically disabled because browsers do not automatically attach custom headers to cross-origin requests.
If you store JWTs in cookies instead of the Authorization header, keep CSRF protection enabled and configure the CookieCsrfTokenRepository:
Session security
Spring Security provides comprehensive session management. For session-based applications (Approach 1), configure the following:
Session fixation protection is enabled by default in Spring Security. When a user logs in, Spring migrates the session (copies attributes to a new session ID), preventing an attacker who knows the pre-login session ID from hijacking the authenticated session.
For session storage, Spring Session provides integrations with Redis, JDBC, and Hazelcast:
Method-level security
Java's annotation-based security is a powerful feature that most other ecosystems lack. Spring Security supports @PreAuthorize, @PostAuthorize, @Secured, and @RolesAllowed for fine-grained authorization at the method level, not just at the URL level.
This is defense-in-depth at its most practical: even if a URL-level security rule is misconfigured, the method-level annotation prevents unauthorized access. The Spring Expression Language (SpEL) expressions in @PreAuthorize allow complex authorization logic, including checking the authenticated user's identity against method parameters.
Quarkus and Micronaut support similar annotations (@RolesAllowed from Jakarta Security, and framework-specific variants), though with less expressive authorization languages than Spring's SpEL.
Security headers
Spring Security sets several security-related HTTP headers by default:
Cache-Control: no-cache, no-store, max-age=0, must-revalidate(prevents caching of authenticated responses)X-Content-Type-Options: nosniff(prevents MIME sniffing)X-Frame-Options: DENY(prevents clickjacking)X-XSS-Protection: 0(disables the browser's XSS auditor, which is considered harmful)Strict-Transport-Security(when HTTPS is enabled)
You can customize these:
Unlike Node.js (where you add Helmet manually), Spring Security includes these protections by default.
Build vs. buy: A realistic comparison
Java's mature ecosystem makes it possible to build a comprehensive authentication system, but the real cost extends well beyond the initial implementation. Email verification, password resets, MFA, OAuth with multiple providers, token refresh and revocation, session management, audit logging, and the ongoing security maintenance to keep everything patched adds up.
Realistic time estimates for building authentication in Java:
- MVP (Spring Security form login): 2 to 5 days.
- Production-ready (with MFA, OAuth, account management): 6 to 10 weeks.
- Enterprise-grade (SSO, SCIM, compliance): 3 to 6 months or more.
- Ongoing maintenance: roughly 20 to 25% of the initial effort each year.
A managed provider compresses most of that into a few hours of integration work and shifts the security maintenance burden off your team. The trade-off is a dependency on an external service, so evaluate based on SDK quality, Spring Security compatibility, pricing at your expected scale, and whether the provider covers the enterprise features your customers will eventually require.
For most B2B SaaS teams, the question is not whether you can build authentication yourself. The question is whether it is the best use of your engineering time.
Production best practices
Security checklist
- Keep Spring Boot, Spring Security, and all dependencies updated. Monitor Spring Security advisories.
- Use
BCryptPasswordEncoderorDelegatingPasswordEncoderfor password hashing. Never store plain text passwords. - Enable CSRF protection for session-based applications. Disable it only for stateless APIs using Bearer tokens.
- Configure session management: set
maximumSessions, enable session fixation protection (enabled by default), and use Redis or JDBC for session storage in multi-instance deployments. - Use
@PreAuthorizeor@Securedannotations for method-level security as a defense-in-depth layer. - Never deserialize untrusted Java objects. Use JSON for data interchange.
- Validate all input with Bean Validation (
@Valid,@NotBlank,@Email) or a schema validation library. - Use parameterized queries via JPA/Hibernate or Spring Data. Never concatenate user input into JPQL or native SQL.
- Run OWASP Dependency-Check (
mvn dependency-check:check) in your CI pipeline. - Store secrets in environment variables, Spring Cloud Config, or a secrets manager (HashiCorp Vault, AWS Secrets Manager). Never commit them to version control.
- Force HTTPS in production. Configure Spring Security's
requiresChannel().anyRequest().requiresSecure()or handle SSL termination at the load balancer. - Rate limit login, registration, and password reset endpoints. Spring Security does not include rate limiting by default; use a library like Bucket4j or implement it with a servlet filter.
- Log authentication events (logins, failures, password changes) using Spring Security's event publishing mechanism and monitor for anomalies.
- Configure security headers. Spring Security sets sensible defaults; add
Content-Security-Policyfor your application's specific needs.
Deployment checklist
- Generate strong, unique secrets for JWT signing, session encryption, and any API keys. Never reuse secrets across environments.
- Configure a production session store (Redis via Spring Session or JDBC) if using session-based authentication.
- Use short-lived access tokens (5 to 15 minutes) and longer-lived refresh tokens (7 to 30 days) for JWT-based APIs.
- Run your application behind a reverse proxy (Nginx, HAProxy, or a cloud load balancer) for SSL termination.
- Enable Spring Boot Actuator health checks and configure your load balancer to use them.
- Configure appropriate JVM memory settings. Spring Security's filter chain adds minimal overhead, but bcrypt hashing is CPU-intensive and benefits from adequate thread pool sizing.
- Set up monitoring with Micrometer and your observability platform. Track authentication endpoint latency, error rates, and failed login patterns.
- Configure automated database backups and test restoration regularly.
- Use Spring Profiles to manage environment-specific security configuration (development, staging, production).
- Test authentication flows with
@SpringBootTestand Spring Security's@WithMockUser,SecurityMockMvcRequestPostProcessors, andWebTestClientsupport.
Conclusion
Java's authentication ecosystem is the most mature and comprehensive of any platform. Spring Security alone provides more built-in security features than most frameworks and their third-party libraries combined. Quarkus and Micronaut offer leaner alternatives for cloud-native deployments without sacrificing the security fundamentals.
If you are building authentication yourself, leverage Spring Security's built-in protections (CSRF, session fixation, security headers, password encoding) rather than reimplementing them. Use method-level security as a defense-in-depth layer. Keep dependencies updated and run security scans in CI.
If you are considering a managed provider, evaluate based on Java SDK quality, Spring Security compatibility, pricing at your expected scale, and whether the provider covers the enterprise features your customers will eventually need.
Authentication is critical infrastructure. Choose the approach that matches where your application is headed, not just where it is today.
Sign up for WorkOS and secure your Java application.