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.