Authentication Mechanisms and Identity Stores
|
This section documents Quarkus 3.39.x (current) with 3.33.x LTS as the production recommendation, on the Java 17+ baseline, as published at the official Quarkus guides — which are the reference these pages are written and verified against. Quarkus 4 raises the baseline to Java 21 (first release candidate planned for end of September 2026, see the Quarkus blog); readers upgrading past 3.39.x/3.33.x LTS should expect that change. This content was generated with the assistance of AI and should be verified against those official guides before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Security Overview and Architecture covers the
shared model every authentication mechanism plugs into — SecurityIdentity, IdentityProvider, and how one mechanism is selected for a given request. This page covers
the concrete mechanisms and identity stores available for authenticating with credentials Quarkus itself
manages — a username/password pair, a client certificate, or a WebAuthn passkey — as opposed to delegating
authentication to an external OpenID Connect provider, which is its
own page. Passwordless and multi-factor theory — what a primary factor is, how a second factor stacks on
top of it — is not repeated here; see
Authentication Methods: Passwordless and
2FA for that.
Basic and form authentication
HTTP Basic
quarkus.http.auth.basic=true enables the RFC 7617 Authorization: Basic scheme against whichever
IdentityProvider is registered (properties file, JDBC, JPA, or LDAP, all below):
quarkus.http.auth.basic=true
Basic sends the username and password on every request, base64-encoded but not encrypted on the wire by the scheme itself — it is only acceptable over TLS, and carries no session or logout concept of its own.
Form-based authentication
Form authentication issues a redirect to a login page and, on success, a session cookie — the traditional browser login flow:
quarkus.http.auth.form.enabled=true
quarkus.http.auth.form.login-page=/login.html
quarkus.http.auth.form.error-page=/login.html?error=true
quarkus.http.auth.form.landing-page=/index.html
quarkus.http.auth.form.cookie-name=quarkus-credential
The submitted form fields (j_username/j_password by default, configurable via
quarkus.http.auth.form.username-parameter/password-parameter) are checked against the same IdentityProvider
mechanisms below would use for Basic auth — form vs. Basic is purely a difference in how credentials are
collected and how the outcome is communicated back to the browser, not a different identity store.
The properties-file identity store
quarkus-elytron-security-properties-file is the simplest identity store — users and roles read from a static
file or inline configuration — appropriate for local development, tests, and small internal tools, never for
production user management:
./mvnw quarkus:add-extension -Dextensions=elytron-security-properties-file
quarkus.security.users.embedded.enabled=true
quarkus.security.users.embedded.plain-text=true
quarkus.security.users.embedded.users.alice=alice-password
quarkus.security.users.embedded.roles.alice=admin,editor
quarkus.security.users.embedded.users.bob=bob-password
quarkus.security.users.embedded.roles.bob=editor
or, from a properties file on the classpath instead of inline configuration:
quarkus.security.users.file.enabled=true
quarkus.security.users.file.users=users.properties
quarkus.security.users.file.roles=roles.properties
quarkus.security.users.file.plain-text=false
with users.properties mapping username=password (or a pre-computed hash when plain-text=false) and
roles.properties mapping username=role1,role2. plain-text=false is strongly preferred outside local
development — it expects a BCrypt hash rather than a plaintext password sitting in a file on disk.
quarkus-security-jdbc
quarkus-security-jdbc authenticates against an existing users/roles table structure via a hand-written SQL
query, over a plain JDBC datasource (see Datasources and
Transactions for configuring that datasource itself):
./mvnw quarkus:add-extension -Dextensions=jdbc-postgresql,security-jdbc
quarkus.security.jdbc.enabled=true
quarkus.security.jdbc.principal-query.sql=\
SELECT u.password, u.role FROM users u WHERE u.username=?
quarkus.security.jdbc.principal-query.clear-password-mapper.enabled=true
quarkus.security.jdbc.principal-query.clear-password-mapper.password-index=1
quarkus.security.jdbc.principal-query.attribute-mappings.0.index=2
quarkus.security.jdbc.principal-query.attribute-mappings.0.to=groups
The query’s own column order drives password-index/attribute-mappings — there is no fixed schema quarkus-
security-jdbc imposes, which makes it the right fit for authenticating against a user table an existing
application already owns, at the cost of writing and maintaining that SQL by hand.
quarkus-security-jpa
quarkus-security-jpa maps authentication onto a Hibernate ORM/Panache entity instead of hand-written SQL — see Hibernate ORM and JPA for the entity mapping this builds
on:
./mvnw quarkus:add-extension -Dextensions=jdbc-postgresql,hibernate-orm-panache,security-jpa
package com.example.security;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import io.quarkus.security.jpa.Password;
import io.quarkus.security.jpa.Roles;
import io.quarkus.security.jpa.UserDefinition;
import io.quarkus.security.jpa.Username;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
@Entity
@Table(name = "app_user")
@UserDefinition
public class AppUser extends PanacheEntity {
@Username
public String username;
@Password
public String password;
@Roles
public String role;
public static void add(String username, String password, String role) {
AppUser user = new AppUser();
user.username = username;
user.password = io.quarkus.elytron.security.common.BcryptUtil.bcryptHash(password);
user.role = role;
user.persist();
}
}
@UserDefinition marks the entity as the identity source; @Username, @Password, and @Roles mark which
fields carry each piece. By default @Password is compared as a BCrypt hash — BcryptUtil.bcryptHash(…)
above produces one at write time, and quarkus-security-jpa verifies it the same way at login time, so a
plaintext password is never stored or compared.
@CustomPasswordProvider
A project using a different hash algorithm (a legacy PBKDF2 or Argon2 hash migrated from another system) plugs
in its own comparison logic via io.quarkus.security.jpa.CustomPasswordProvider:
package com.example.security;
import io.quarkus.security.jpa.CustomPasswordProvider;
import io.quarkus.security.jpa.Password;
public class Argon2PasswordProvider implements CustomPasswordProvider<AppUser> {
@Override
public Password getPassword(AppUser user) {
return new Password(user.password);
}
}
referenced from the entity as @Password(provider = Argon2PasswordProvider.class), with the actual Argon2
verification performed inside the provider rather than relying on `quarkus-security-jpa’s built-in BCrypt
comparator.
LDAP
quarkus-elytron-security-ldap authenticates against an existing LDAP or Active Directory directory — appropriate when user accounts are already centrally managed there rather than in an application-owned table:
./mvnw quarkus:add-extension -Dextensions=elytron-security-ldap
quarkus.security.ldap.dir-context.url=ldap://ldap.example.com:389
quarkus.security.ldap.dir-context.principal=cn=admin,dc=example,dc=com
quarkus.security.ldap.dir-context.password=admin-password
quarkus.security.ldap.identity-mapping.rdn-identifier=uid
quarkus.security.ldap.identity-mapping.search-base-dn=ou=users,dc=example,dc=com
quarkus.security.ldap.identity-mapping.attribute-mappings.0.from=cn
quarkus.security.ldap.identity-mapping.attribute-mappings.0.to=groups
quarkus.security.ldap.identity-mapping.attribute-mappings.0.filter=(member={0})
quarkus.security.ldap.identity-mapping.attribute-mappings.0.filter-base-dn=ou=groups,dc=example,dc=com
dir-context. configures the service account Quarkus uses to bind and search the directory;
identity-mapping. configures how a username resolves to a directory entry (rdn-identifier plus
search-base-dn) and how that entry’s group memberships map onto SecurityIdentity roles.
WebAuthn and passkeys
quarkus-webauthn implements the WebAuthn/FIDO2 ceremonies — registration and assertion — against a
browser’s platform authenticator (a passkey backed by Face ID, Windows Hello, a hardware security key), storing
each user’s public-key credential rather than a password at all:
./mvnw quarkus:add-extension -Dextensions=webauthn
quarkus.webauthn.relying-party-id=example.com
quarkus.webauthn.relying-party-name=Example App
quarkus.webauthn.origin=https://example.com
A WebAuthnUserProvider CDI bean (application-supplied) persists each user’s WebAuthn credential records
(public key, sign counter) after the browser-side registration ceremony completes, and looks them up again on
each subsequent assertion; the browser-side JavaScript ceremony itself (navigator.credentials.create()/.get())
sits outside the Quarkus extension’s scope entirely. Passkeys are the strongest and most phishing-resistant
primary-authentication method covered on this page — the origin-bound cryptographic challenge that WebAuthn
performs makes credential phishing structurally impossible in a way no password-based mechanism can match; see
Authentication Methods: Passwordless and
2FA for how passkeys compare to the other passwordless and 2FA options along both of that page’s axes.
Mutual TLS (mTLS)
Mutual TLS authenticates the client during the TLS handshake itself, via a client certificate, rather than through any application-layer credential — configured on the TLS registry with client-certificate authentication turned on:
quarkus.tls.https.trust-store.p12.path=truststore.p12
quarkus.tls.https.trust-store.p12.password=${truststore-password}
quarkus.http.ssl.client-auth=required
client-auth=required rejects the TLS handshake outright for a client that presents no certificate, or one not
signed by a CA in the configured trust store; client-auth=request accepts the connection either way but still
surfaces the certificate (if any) for the IdentityProvider mechanism below to consume. quarkus-elytron-
security-ldap’s mTLS support (or a custom `IdentityProvider reading HttpAuthenticationMechanism’s
`X509Certificate from the routing context) maps the certificate’s subject onto a SecurityIdentity; because
authentication concludes at the TLS layer, mTLS composes cleanly with a quarkus.http.auth.permission.* policy
restricting only the paths that need certificate-based clients, while other paths on the same server continue
authenticating some other way.
Choosing and combining mechanisms per path
More than one mechanism can be active at once — each HttpAuthenticationMechanism only actually engages for a
request that carries its own kind of credential, so registering quarkus-elytron-security-properties-file
(Basic) alongside quarkus-oidc (bearer JWT) does not force every client onto one scheme. Where mechanisms
would otherwise be ambiguous for the same path, scope each explicitly with quarkus.http.auth.permission.* path
matching (see Security Overview and Architecture) so
a browser-facing path uses form/session authentication while a machine-facing API path under the same
application uses bearer tokens. The general rule for choosing among the mechanisms on this page: reach for the
properties-file store only in development/tests; JDBC when an existing hand-rolled user table must be reused
as-is; JPA when the identity store can be modeled as a first-class entity in the application’s own domain;
LDAP when accounts are already centrally managed there; WebAuthn for the strongest phishing resistance available
to end users; and mTLS for service-to-service or highly-controlled-client scenarios where a certificate can
actually be provisioned to every legitimate caller.
Migrating off the Elytron-prefixed extension names
Older material — including the Quarkus Cookbook — documents identity stores under names that have since
been renamed or reorganized: quarkus-elytron-security-jdbc is now quarkus-security-jdbc, and
quarkus-elytron-security-jpa is now quarkus-security-jpa. quarkus-elytron-security-properties-file and
quarkus-elytron-security-ldap keep their original elytron-prefixed names (they were not renamed), so the
elytron prefix by itself is not a reliable signal that an extension is legacy — what changed is specifically
the JDBC and JPA identity-store artifact names. A project built against the older names should update its
pom.xml/build.gradle dependency coordinates and, where the guide being followed still shows
quarkus-elytron-security-jdbc/-jpa, treat the current JDBC guide
and the current JPA guide as authoritative over the older text.