Enterprise Java applications often get authentication right in one place and wrong in three others: an uncovered HTTP method, a password hash at the specification's minimum work factor, or an OpenID Connect client secret compiled into an annotation. Jakarta Security is the standard, portable API for authentication and identity in Jakarta EE, sitting on top of the lower-level Jakarta Authentication and Jakarta Authorization specifications. This guide explains how those pieces fit together in Jakarta EE 11 (Jakarta Security 4.0), how to choose and configure the built-in authentication mechanisms and identity stores, how to keep administrative and role logic tight, how to handle the service credentials the configuration needs, and what to log. It is written for Java developers, application architects and AppSec engineers working on GlassFish, WildFly, JBoss EAP, Open Liberty, Payara or any other compatible runtime. Spring applications are covered in the Java and Spring Security Guide.
This is an independent guide from NHI Mgmt Group, based on the Eclipse Foundation's Jakarta EE public documentation as of November 2026. It is not sponsored or reviewed by the Eclipse Foundation. Product features change, so confirm settings against the Jakarta EE current documentation.
Key takeaways
- Jakarta Security is the layer to code against. Use
HttpAuthenticationMechanism,IdentityStoreandSecurityContextinstead of server-specific realm APIs, and drop down to Jakarta Authentication only for custom mechanisms. - Raise the PBKDF2 work factor. The built-in
Pbkdf2PasswordHashdefaults to 2,048 iterations of PBKDF2WithHmacSHA256; OWASP recommends 600,000 for that algorithm. - Never hard-code secrets in annotations. The OpenID Connect
clientSecretand LDAPbindDnPasswordare plain annotation strings; resolve them through expressions from configuration or a vault. - Close uncovered HTTP methods. A security constraint that lists only GET and POST leaves PUT, DELETE and others unprotected unless
<deny-uncovered-http-methods/>is set. - Logging is the server's job, not the specification's. Jakarta Security defines no audit format, so plan login, failure and role-change logging in the runtime and the application.
How identity and access work in Jakarta EE
Jakarta EE 11 requires Java SE 17 or later. The release page lists Jakarta Security 4.0, Jakarta Authentication 3.1 and Servlet 6.1 in the Web Profile, Jakarta Authorization 3.0 under the full Platform, and Jakarta Annotations 3.0 (which provides @RolesAllowed) in the Core Profile. Each layer has a distinct job.
| Specification | Version (EE 11) | Role | What developers touch |
|---|---|---|---|
| Jakarta Security | 4.0 | Portable, CDI-based authentication and identity API | HttpAuthenticationMechanism, IdentityStore, SecurityContext, built-in mechanism and store annotations |
| Jakarta Authentication | 3.1 | Low-level SPI that connects authentication mechanisms to the container | ServerAuthModule for custom, non-HTTP or unusual mechanisms |
| Jakarta Authorization | 3.0 | Container authorisation contract and policy providers | Rarely touched directly; 3.0 allows per-application policy providers registered programmatically |
| Servlet | 6.1 | Web security constraints, sessions, cookies | web.xml security-constraint, @ServletSecurity, session cookie configuration |
| Jakarta Annotations | 3.0 | Declarative role checks on beans | @RolesAllowed, @PermitAll, @DenyAll |
Version 4.0 added a handler API for using several authentication mechanisms in one application, CDI qualifiers on the built-in mechanisms, and an in-memory identity store, and removed references to the Java SecurityManager. The compatible implementation is Eclipse Soteria. Version 5.0 for Jakarta EE 12 is under development, so check the specification page before relying on newer features.
Built-in authentication mechanisms
| Annotation | Use it for | Security notes |
|---|---|---|
@BasicAuthenticationMechanismDefinition | Internal tools and machine clients during migration | Credentials on every request; only over TLS; no logout; vulnerable to password spraying (T1110.003) |
@FormAuthenticationMechanismDefinition | Classic server-rendered login pages | Uses @LoginToContinue, defaults /login and /login-error; protect the form with CSRF controls and rate limiting |
@CustomFormAuthenticationMechanismDefinition | Login pages backed by a Jakarta Faces or REST call that invokes SecurityContext.authenticate() | Lets the application add MFA or lockout logic before authentication completes |
@OpenIdAuthenticationMechanismDefinition | Single sign-on with an external OpenID Connect provider | Defaults: response_type=code, scopes openid, email and profile, nonce on, state validated, redirect URI ${baseURL}/Callback |
Built-in identity stores
- Database store:
@DatabaseIdentityStoreDefinitiontakes adataSourceLookup(defaultjava:comp/DefaultDataSource), acallerQuerythat returns the password hash and agroupsQuery. Hashing defaults toPbkdf2PasswordHash. - LDAP store:
@LdapIdentityStoreDefinitiontakes a URL, an optionalbindDnandbindDnPassword, caller and group search bases and filters,maxResults(default 1,000) andreadTimeout(default 0, meaning no timeout). See the LDAP Security Deep Dive Guide. - In-memory store:
@InMemoryIdentityStoreDefinitionholds users in the annotation. The specification says it is provided for testing, debugging and demos and is not generally advised for production. - Ordering: stores run in priority order (lower first; LDAP defaults to 80, database to 70), and
useForseparates credential validation from group lookup, so one store can authenticate and another can supply groups.
Admin roles and privileged access
Jakarta EE has no built-in "administrator": privilege is whatever role names the application declares and maps to groups. That makes role design and mapping the main privileged access control.
- Keep role names few and explicit: declare administrative roles separately (for example
app-admin,billing-admin) rather than reusing a broadadmingroup from the directory.SecurityContext.getAllDeclaredCallerRoles(), added in 4.0, lists the static roles a caller holds and is useful for access reviews and debugging. - Map groups to roles deliberately: with the OpenID Connect mechanism, groups come from the
groupsclaim and the caller name frompreferred_usernameby default. Confirm that your provider issues those claims only from authoritative sources, and do not map a group that users can join themselves. - Enforce at every layer: use URL constraints for coarse protection,
@RolesAllowedon bean methods for business operations, and object-level checks in code for data ownership. A role check at the servlet layer does not protect a REST resource reached through another path. See the Broken Access Control Prevention Guide. - Default deny on beans: put
@DenyAllat class level on sensitive beans and open individual methods with@RolesAllowed, so a new method is protected by default. - Use step-up for dangerous actions: a custom form mechanism or an
HttpAuthenticationMechanismcan require re-authentication or an MFA claim before admin operations such as role changes or exports. - Separate the server's own admins: application roles are distinct from application server management users. Harden the runtime's management interface separately; see the JBoss EAP and WildFly Hardening and the Apache Tomcat Hardening Guide.
Integrations, API tokens and service accounts
A Jakarta Security configuration typically depends on three non-human credentials: the database account behind the identity store's data source, the LDAP bind account and the OpenID Connect client secret. Each is a long-lived secret held by the application.
| Credential | Where it is configured | Risk | Control |
|---|---|---|---|
| OpenID Connect client secret | clientSecret on @OpenIdAuthenticationMechanismDefinition | The specification's own Javadoc strongly recommends setting it through an expression so the value is not hard-coded | Use an expression such as ${openIdConfig.clientSecret} backed by a CDI bean that reads a vault or MicroProfile Config source |
| LDAP bind password | bindDnPassword on @LdapIdentityStoreDefinition | Plain string in source; bind accounts often have broad directory read rights | Read-only bind account restricted to the search bases used; secret injected at runtime; LDAPS only |
| Identity store database account | Data source in the server configuration | Read access to every password hash | Dedicated account with SELECT on the user and group tables only; password from the server's credential store |
| Remember-me tokens | @RememberMe with an application RememberMeIdentityStore | Bearer token in a cookie (default name JREMEMBERMEID, max age 86,400 seconds) | Store only a hash of the token server side, rotate on use, revoke on password change |
- Scan for leaks: annotation values end up in source control and in compiled classes. Scan both. See the Secrets Management Guide.
- Machine clients: for service-to-service calls, prefer OAuth 2.0 client credentials or mutual TLS validated by a custom mechanism over Basic authentication with a shared service password. See the OAuth 2.0 and OpenID Connect Guide and the Java Keystores Guide.
- Token refresh:
tokenAutoRefreshis off by default, so sessions keep running on the original authentication until the session ends. Decide explicitly whether access token expiry should end or refresh the session.
Logging and detection
Jakarta Security does not define audit events. The runtime (for example Elytron audit logging in WildFly and JBoss EAP) and your own code must produce them. A practical approach is to log from a CDI decorator or wrapper around your HttpAuthenticationMechanism and from role-change code paths.
| Event | Where to capture it | Related technique |
|---|---|---|
| Authentication success and failure with caller name, mechanism and source IP | Server security audit log or mechanism wrapper | T1110 Brute Force, T1078 Valid Accounts |
| Many failures across many usernames from one source | SIEM correlation on failure events | T1110.003 Password Spraying |
| OpenID Connect callback failures (state mismatch, invalid token) | Mechanism logs | Login CSRF or token injection attempts |
| Remember-me token reuse from a new device or IP | Application RememberMeIdentityStore | T1539 Steal Web Session Cookie |
| 403 responses on unusual HTTP methods | Access logs | Probing for uncovered methods |
| Admin role grants and changes | Application audit trail | T1078 Valid Accounts |
| Exploit-style payloads against public endpoints | Reverse proxy, WAF | T1190 Exploit Public-Facing Application |
Never log passwords, tokens, the remember-me cookie or the full OpenID Connect callback query string, which contains the authorisation code.
Metrics that show it is working
- Hash strength: share of stored hashes at the target iteration count (rehash on next login until 100 percent).
- Secret hygiene: number of literal secrets found in annotations or configuration by scanning (target: zero).
- Constraint coverage: applications with
deny-uncovered-http-methodsset and no container warnings about uncovered methods. - Authentication mix: share of logins through OpenID Connect versus local password stores.
- Failure rate: authentication failures per hour and per source, with alert thresholds.
Hardening baseline
| Area | Setting | Detail |
|---|---|---|
| Password hashing | hashAlgorithmParameters on the database store | Set Pbkdf2PasswordHash.Iterations=600000 with PBKDF2WithHmacSHA256 (or 220,000 with SHA512); defaults are 2,048 iterations, 32-byte salt, 32-byte key. Hashes are stored as algorithm:iterations:salt:hash, so old hashes still verify while you upgrade |
| HTTP methods | <deny-uncovered-http-methods/> in web.xml | The container must reject uncovered methods with 403 |
| Transport | transport-guarantee CONFIDENTIAL or @ServletSecurity(@HttpConstraint(transportGuarantee = TransportGuarantee.CONFIDENTIAL)) | Forces HTTPS for protected resources |
| Session cookie | session-config cookie http-only and secure, tracking mode COOKIE | Avoids session IDs in URLs; see the Cookie Security Guide |
| Session fixation | New session ID after login | Call HttpServletRequest.changeSessionId() in custom flows if the container does not |
| Remember-me | cookieSecureOnly and cookieHttpOnly true (defaults), shorter cookieMaxAgeSeconds for sensitive apps | Default lifetime is one day |
| OpenID Connect | Keep useNonce true; exact redirect URI registered at the provider; secret via expression | Defaults are sound; do not disable nonce for convenience |
| LDAP | ldaps:// URL, non-zero readTimeout, scoped search bases | Default read timeout is unlimited |
| Identity stores | No in-memory store in production builds | Exclude test configuration from release artefacts |
| Expression Language | Never pass user input into EL evaluation | EL injection leads to code execution; see the Java Secure Coding Guidelines Guide |
Phased rollout
- Inventory: list each application's mechanism, identity stores, declared roles, security constraints and the credentials it holds.
- Quick wins: add
deny-uncovered-http-methods, transport guarantees and cookie flags in a test environment; read the container's deployment log for uncovered method warnings. - Secrets: move client secrets, bind passwords and data source passwords out of annotations and files into runtime configuration or a vault.
- Hashing: raise the iteration count and rehash on successful login; measure login latency under load first.
- Federation: move user-facing apps to the OpenID Connect mechanism so MFA and lifecycle sit at the identity provider; keep a local break-glass admin.
- Logging: add mechanism wrappers and role-change audit events, then build the detections above.
Testing and rollback: run integration tests that send every HTTP method to each protected URL and expect 401 or 403. Ship constraint and mechanism changes behind a deployment you can roll back as a whole. The hash format records the iteration count per hash, so raising iterations does not lock out existing users and can be reverted.
Small versus large teams: a single application can use the database or LDAP store directly. Organisations with many applications should standardise on OpenID Connect to a central provider and keep local stores only for break-glass access.
Common misconfigurations
- Method lists in security constraints: constraining GET and POST only, so DELETE or PUT on the same URL is unauthenticated.
- Default PBKDF2 iterations: 2,048 iterations makes offline cracking of a stolen user table far cheaper than OWASP's guidance assumes. See the Password Hashing and Key Derivation Guide.
- Literal secrets in annotations: client secrets and bind passwords committed to Git and visible in compiled classes (T1552.001 Credentials In Files).
- In-memory store left in production: demo users with known passwords shipped in a release build.
- Role checks only at the URL layer: beans and REST resources reachable through other paths without
@RolesAllowed. - Basic authentication on internet-facing endpoints: no lockout or MFA, an easy target for credential stuffing.
Framework mapping
| Control area | NIST SP 800-53 Rev. 5 | NIST CSF 2.0 | OWASP ASVS 5.0 | ISO/IEC 27001:2022 Annex A |
|---|---|---|---|---|
| Authentication mechanisms and federation | IA-2, IA-5 | PR.AA-03 | V6 Authentication | A.8.5 |
| Password storage | IA-5(1) | PR.AA-01 | V11.4.2 | A.5.17 |
| Roles and constraints | AC-3, AC-6 | PR.AA-05 | V8 Authorization | A.8.3 |
| Transport and sessions | SC-8, SC-23 | PR.DS-02 | V7 Session Management | A.8.24 |
| Audit events | AU-2, AU-12 | DE.CM-09 | V16 Security Logging and Error Handling | A.8.15 |
Practitioner checklist
- Code authentication against Jakarta Security APIs, not server-specific realm classes.
- Set
Pbkdf2PasswordHash.Iterationsto at least 600,000 for PBKDF2WithHmacSHA256 and rehash on login. - Resolve
clientSecret,bindDnPasswordand data source passwords from runtime configuration or a vault. - Add
<deny-uncovered-http-methods/>and CONFIDENTIAL transport guarantees to every web module. - Put
@DenyAllon sensitive beans and grant methods with@RolesAllowed. - Remove in-memory identity stores from production builds.
- Use LDAPS, a read-only bind account and a non-zero LDAP read timeout.
- Prefer the OpenID Connect mechanism with nonce and an exact redirect URI for user logins.
- Log authentication results and role changes without credentials, and alert on spraying patterns.
Standards and references
- Jakarta EE: Jakarta Security 4.0
- Jakarta EE: Jakarta Security specification and API source
- Jakarta EE: Jakarta Authentication 3.1
- Jakarta EE: Jakarta Authorization 3.0
- Jakarta EE: Jakarta EE 11 release
- Jakarta EE: Servlet specification source
- OWASP: Password Storage Cheat Sheet
- OWASP: Application Security Verification Standard 5.0
Related NHI Mgmt Group resources: Java and Spring Security Guide · JBoss EAP and WildFly Hardening · OAuth 2.0 and OpenID Connect Guide · Password Hashing and Key Derivation Guide · OWASP ASVS Guide