이 콘텐츠는 선택한 언어로 제공되지 않습니다.
Security architecture
Abstract
Providing feedback on Red Hat build of Quarkus documentation
To report an error or to improve our documentation, log in to your Red Hat Jira account and submit an issue. If you do not have a Red Hat Jira account, then you will be prompted to create an account.
Procedure
- Click the following link to create a ticket.
- Enter a brief description of the issue in the Summary.
- Provide a detailed description of the issue or enhancement in the Description. Include a URL to where the issue occurs in the documentation.
- Clicking Submit creates and routes the issue to the appropriate documentation team.
Chapter 1. Quarkus Security architecture
The Quarkus Security architecture provides several built-in authentication mechanisms and is highly customizable. The primary mechanism for securing HTTP applications in Quarkus is the HttpAuthenticationMechanism
interface.
1.1. Overview of the Quarkus Security architecture
When a client sends a HTTP request, Quarkus Security orchestrates security authentication and authorization by interacting with several built-in core components, including HttpAuthenticationMechanism
, IdentityProvider
, and SecurityIdentityAugmentor
.
The sequential security validation process results in one of three outcomes:
- The HTTP request gets authenticated and authorized, and access to the Quarkus application gets granted.
-
The HTTP request authentication fails, and the requester receives a challenge specific to the authentication mechanism, for example, a
401
error, a URL redirect to reauthenticate, or some other custom authentication challenge response. For practical examples of challenge responses, see the Quarkus Security Tips and Tricks guide. - The HTTP request authorization fails, and the requester gets denied access to the Quarkus application.
The following diagram steps through the detailed process flow of the Quarkus Security architecture:
Figure 1.1. The Quarkus Security architecture and process flow

1.2. Core components of the Quarkus Security architecture
1.2.1. HttpAuthenticationMechanism
Quarkus Security uses HttpAuthenticationMechanism
to extract the authentication credentials from the HTTP request and delegates them to IdentityProvider
to convert the credentials to SecurityIdentity
. For example, the credentials can come from the Authorization
header, client HTTPS certificates, or cookies.
When Quarkus Security rejects an authentication request, HttpAuthenticationMechanism
returns an authentication challenge to the client. The type of challenge depends on the authentication mechanism. For example, with the OIDC OpenID Connect (OIDC) Authorization Code Flow mechanism, a redirect URL gets generated, and the client is returned to the OpenID Connect provider to authenticate.
1.2.2. IdentityProvider
IdentityProvider
verifies the authentication credentials and maps them to SecurityIdentity
, which has the username, roles, original authentication credentials, and other attributes.
You can inject a SecurityIdentity
instance for every authenticated resource to get the authenticated identity information.
In other contexts, it is possible to have other parallel representations of the same information or parts of it, for example, SecurityContext
for Jakarta REST or JsonWebToken
for JSON Web Tokens (JWT).
For more information, see the Quarkus Identity providers guide.
1.2.3. SecurityIdentityAugmentor
Because Quarkus Security is customizable, you can, for example, add authorization roles to SecurityIdentity
and register and prioritize one or more SecurityAugmentor
implementations.
Registered instances of SecurityIdentityAugmentor
are invoked during the final stage of the security authentication process. For more information, see the Security Identity Customization section of the "Security Tips and Tricks" guide.
1.3. Supported authentication mechanisms
The Quarkus Security framework supports multiple authentication mechanisms, which can also be combined. Some supported authentication mechanisms are built into Quarkus, while others require you to add an extension.
To learn about security authentication in Quarkus and the supported mechanisms and protocols, see the Quarkus Authentication mechanisms in Quarkus guide.
1.4. Proactive authentication
Proactive authentication is enabled in Quarkus by default. The request is always authenticated if an incoming request has a credential, even if the target page does not require authentication. For more information, see the Quarkus Proactive authentication guide.
1.5. Quarkus Security customization
Quarkus Security is customizable. You can customize the following core security components of Quarkus:
-
HttpAuthenticationMechanism
-
IdentityProvider
-
SecurityidentityAugmentor
For more information about customizing Quarkus Security, including reactive security and how to register a security provider, see the Quarkus Security tips and tricks guide.
1.6. References
Chapter 2. Authentication mechanisms in Quarkus
The Quarkus Security framework supports multiple authentication mechanisms, which you can use to secure your applications. You can also combine authentication mechanisms.
Before you choose an authentication mechanism for securing your Quarkus applications, review the information provided.
2.1. Overview of supported authentication mechanisms
Some supported authentication mechanisms are built into Quarkus, while others require you to add an extension. All of these mechanisms are detailed in the following sections:
The following table maps specific authentication requirements to a supported mechanism that you can use in Quarkus:
Authentication requirement | Authentication mechanism |
---|---|
Username and password | |
Bearer access token | |
Single sign-on (SSO) | |
Client certificate |
For more information, see the following Token authentication mechanism comparison table.
2.2. Built-in authentication mechanisms
Quarkus Security provides the following built-in authentication support:
2.2.1. Basic authentication
You can secure your Quarkus application endpoints with the built-in HTTP Basic authentication mechanism. For more information, see the following documentation:
2.2.2. Form-based authentication
Quarkus provides form-based authentication that works similarly to traditional Servlet form-based authentication. Unlike traditional form authentication, the authenticated user is not stored in an HTTP session because Quarkus does not support clustered HTTP sessions. Instead, the authentication information is stored in an encrypted cookie, which can be read by all cluster members who share the same encryption key.
To apply encryption, add the quarkus.http.auth.session.encryption-key
property, and ensure the value you set is at least 16 characters long. The encryption key is hashed by using SHA-256. The resulting digest is used as a key for AES-256 encryption of the cookie value. The cookie contains an expiry time as part of the encrypted value, so all nodes in the cluster must have their clocks synchronized. At one-minute intervals, a new cookie gets generated with an updated expiry time if the session is in use.
To get started with form authentication, you should have similar settings as described in Enable Basic authentication and property quarkus.http.auth.form.enabled
must be set to true
.
Simple application.properties
with form-base authentication can look similar to this:
quarkus.http.auth.form.enabled=true quarkus.http.auth.form.login-page=login.html quarkus.http.auth.form.landing-page=hello quarkus.http.auth.form.error-page= # Define testing user quarkus.security.users.embedded.enabled=true quarkus.security.users.embedded.plain-text=true quarkus.security.users.embedded.users.alice=alice quarkus.security.users.embedded.roles.alice=user
quarkus.http.auth.form.enabled=true
quarkus.http.auth.form.login-page=login.html
quarkus.http.auth.form.landing-page=hello
quarkus.http.auth.form.error-page=
# Define testing user
quarkus.security.users.embedded.enabled=true
quarkus.security.users.embedded.plain-text=true
quarkus.security.users.embedded.users.alice=alice
quarkus.security.users.embedded.roles.alice=user
Configuring user names, secrets, and roles in the application.properties file is appropriate only for testing scenarios. For securing a production application, it is crucial to use a database or LDAP to store this information. For more information you can take a look at Quarkus Security with Jakarta Persistence or other mentioned in Enable Basic authentication.
and application login page will contain HTML form similar to this:
<form action="/j_security_check" method="post"> <label>Username</label> <input type="text" placeholder="Username" name="j_username" required> <label>Password</label> <input type="password" placeholder="Password" name="j_password" required> <button type="submit">Login</button> </form>
<form action="/j_security_check" method="post">
<label>Username</label>
<input type="text" placeholder="Username" name="j_username" required>
<label>Password</label>
<input type="password" placeholder="Password" name="j_password" required>
<button type="submit">Login</button>
</form>
With single-page applications (SPA), you typically want to avoid redirects by removing default page paths, as shown in the following example:
do not redirect, respond with HTTP 200 OK do not redirect, respond with HTTP 401 Unauthorized HttpOnly must be false if you want to log out on the client; it can be true if logging out from the server
# do not redirect, respond with HTTP 200 OK
quarkus.http.auth.form.landing-page=
# do not redirect, respond with HTTP 401 Unauthorized
quarkus.http.auth.form.login-page=
quarkus.http.auth.form.error-page=
# HttpOnly must be false if you want to log out on the client; it can be true if logging out from the server
quarkus.http.auth.form.http-only-cookie=false
Now that you have disabled redirects for the SPA, you must log in and log out programmatically from your client. Below are examples of JavaScript methods for logging into the j_security_check
endpoint and logging out of the application by destroying the cookie.
const login = () => { // Create an object to represent the form data const formData = new URLSearchParams(); formData.append("j_username", username); formData.append("j_password", password); // Make an HTTP POST request using fetch against j_security_check endpoint fetch("j_security_check", { method: "POST", body: formData, headers: { "Content-Type": "application/x-www-form-urlencoded", }, }) .then((response) => { if (response.status === 200) { // Authentication was successful console.log("Authentication successful"); } else { // Authentication failed console.error("Invalid credentials"); } }) .catch((error) => { console.error(error); }); };
const login = () => {
// Create an object to represent the form data
const formData = new URLSearchParams();
formData.append("j_username", username);
formData.append("j_password", password);
// Make an HTTP POST request using fetch against j_security_check endpoint
fetch("j_security_check", {
method: "POST",
body: formData,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
})
.then((response) => {
if (response.status === 200) {
// Authentication was successful
console.log("Authentication successful");
} else {
// Authentication failed
console.error("Invalid credentials");
}
})
.catch((error) => {
console.error(error);
});
};
To log out of the SPA from the client, the cookie must be set to quarkus.http.auth.form.http-only-cookie=false
so you can destroy the cookie and possibly redirect back to your main page.
const logout= () => { // delete the credential cookie, essentially killing the session const removeCookie = `quarkus-credential=; Max-Age=0;path=/`; document.cookie = removeCookie; // perform post-logout actions here, such as redirecting back to your login page };
const logout= () => {
// delete the credential cookie, essentially killing the session
const removeCookie = `quarkus-credential=; Max-Age=0;path=/`;
document.cookie = removeCookie;
// perform post-logout actions here, such as redirecting back to your login page
};
To log out of the SPA from the server, the cookie can be set to quarkus.http.auth.form.http-only-cookie=true
and use this example code to destroy the cookie.
import io.quarkus.security.identity.CurrentIdentityAssociation; import io.quarkus.vertx.http.runtime.security.FormAuthenticationMechanism; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.POST; @Inject CurrentIdentityAssociation identity; @POST public Response logout() { if (identity.getIdentity().isAnonymous()) { throw new UnauthorizedException("Not authenticated"); } FormAuthenticationMechanism.logout(identity.getIdentity()); return Response.noContent().build(); }
import io.quarkus.security.identity.CurrentIdentityAssociation;
import io.quarkus.vertx.http.runtime.security.FormAuthenticationMechanism;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.POST;
@Inject
CurrentIdentityAssociation identity;
@POST
public Response logout() {
if (identity.getIdentity().isAnonymous()) {
throw new UnauthorizedException("Not authenticated");
}
FormAuthenticationMechanism.logout(identity.getIdentity());
return Response.noContent().build();
}
- 1
- Perform the logout by removing the session cookie.
The following properties can be used to configure form-based authentication:
Configuration property fixed at build time - All other configuration properties are overridable at runtime
Configuration property | Type | Default |
Determines whether the entire permission set is enabled, or not. By default, if the permission set is defined, it is enabled.
Environment variable: | boolean | |
The HTTP policy that this permission set is linked to. There are three built-in policies: permit, deny and authenticated. Role based policies can be defined, and extensions can add their own policies.
Environment variable: | string |
required
|
The methods that this permission set applies to. If this is not set then they apply to all methods. Note that if a request matches any path from any permission set, but does not match the constraint due to the method not being listed then the request will be denied. Method specific permissions take precedence over matches that do not have any methods set. This means that for example if Quarkus is configured to allow GET and POST requests to /admin to and no other permissions are configured PUT requests to /admin will be denied.
Environment variable: | list of string | |
The paths that this permission check applies to. If the path ends in /* then this is treated as a path prefix, otherwise it is treated as an exact match. Matches are done on a length basis, so the most specific path match takes precedence. If multiple permission sets match the same path then explicit methods matches take precedence over matches without methods set, otherwise the most restrictive permissions are applied.
Environment variable: | list of string | |
Path specific authentication mechanism which must be used to authenticate a user. It needs to match
Environment variable: | string | |
Indicates that this policy always applies to the matched paths in addition to the policy with a winning path. Avoid creating more than one shared policy to minimize the performance impact.
Environment variable: | boolean |
|
Whether permission check should be applied on all matching paths, or paths specific for the Jakarta REST resources.
Environment variable: | all: Apply on all matching paths.
jaxrs: Declares that a permission check must only be applied on the Jakarta REST request paths. Use this option to delay the permission check if an authentication mechanism is chosen with an annotation on the matching Jakarta REST endpoint. This option must be set if the following REST endpoint annotations are used: - | all |
The roles that are allowed to access resources protected by this policy. By default, access is allowed to any authenticated user.
Environment variable: | list of string |
|
Add roles granted to the
Environment variable: | Map<String,List<String>> | |
Permissions granted to the
Environment variable: | Map<String,List<String>> | |
Permissions granted by this policy will be created with a
Environment variable: | string |
|
Map the
For example, if
Environment variable: | Map<String,List<String>> | |
Client certificate attribute whose values are going to be mapped to the 'SecurityIdentity' roles according to the roles mapping specified in the certificate properties file. The attribute must be either one of the Relative Distinguished Names (RDNs) or Subject Alternative Names (SANs). By default, the Common Name (CN) attribute value is used for roles mapping. Supported values are:
Environment variable: | string |
|
Properties file containing the client certificate attribute value to role mappings. Use it only if the mTLS authentication mechanism is enabled with either
Properties file is expected to have the
Environment variable: | path | |
The authentication realm
Environment variable: | string | |
The login page. Redirect to login page can be disabled by setting
Environment variable: | string |
|
The username field name.
Environment variable: | string |
|
The password field name.
Environment variable: | string |
|
The error page. Redirect to error page can be disabled by setting
Environment variable: | string |
|
The landing page to redirect to if there is no saved page to redirect back to. Redirect to landing page can be disabled by setting
Environment variable: | string |
|
Option to control the name of the cookie used to redirect the user back to the location they want to access.
Environment variable: | string |
|
The inactivity (idle) timeout When inactivity timeout is reached, cookie is not renewed and a new login is enforced.
Environment variable: |
| |
How old a cookie can get before it will be replaced with a new cookie with an updated timeout, also referred to as "renewal-timeout". Note that smaller values will result in slightly more server load (as new encrypted cookies will be generated more often); however, larger values affect the inactivity timeout because the timeout is set when a cookie is generated. For example if this is set to 10 minutes, and the inactivity timeout is 30m, if a user’s last request is when the cookie is 9m old then the actual timeout will happen 21m after the last request because the timeout is only refreshed when a new cookie is generated. That is, no timeout is tracked on the server side; the timestamp is encoded and encrypted in the cookie itself, and it is decrypted and parsed with each request.
Environment variable: |
| |
The cookie that is used to store the persistent session
Environment variable: | string |
|
The cookie path for the session and location cookies.
Environment variable: | string |
|
Set the HttpOnly attribute to prevent access to the cookie via JavaScript.
Environment variable: | boolean |
|
SameSite attribute for the session and location cookies.
Environment variable: |
|
|
Max-Age attribute for the session cookie. This is the amount of time the browser will keep the cookie. The default value is empty, which means the cookie will be kept until the browser is closed.
Environment variable: | ||
Require that all registered HTTP authentication mechanisms must attempt to verify the request credentials.
By default, when the All produced security identities can be retrieved using the following utility method: `io.quarkus.vertx.http.runtime.security.HttpSecurityUtils++#++getSecurityIdentities(io.quarkus.security.identity.SecurityIdentity)`
An injected
This property is false by default which means that the authentication process is complete as soon as the first This property will be ignored if the path specific authentication is enabled.
Environment variable: | boolean |
|
Inclusive authentication mode.
Environment variable: | lax: Authentication succeeds if at least one of the registered HTTP authentication mechanisms creates the identity.
strict: Authentication succeeds if all the registered HTTP authentication mechanisms create the identity. Typically, inclusive authentication should be in the strict mode when the credentials are carried over mTLS, when both mTLS and another authentication, for example, OIDC bearer token authentication, must succeed. In such cases, | strict |
To write duration values, use the standard java.time.Duration
format. See the Duration#parse() Java API documentation for more information.
You can also use a simplified format, starting with a number:
- If the value is only a number, it represents time in seconds.
-
If the value is a number followed by
ms
, it represents time in milliseconds.
In other cases, the simplified format is translated to the java.time.Duration
format for parsing:
-
If the value is a number followed by
h
,m
, ors
, it is prefixed withPT
. -
If the value is a number followed by
d
, it is prefixed withP
.
2.2.3. Mutual TLS authentication
Quarkus provides mutual TLS (mTLS) authentication so that you can authenticate users based on their X.509 certificates.
To use this authentication method, you must first enable SSL/TLS for your application. For more information, see the Supporting secure connections with SSL/TLS section of the Quarkus "HTTP reference" guide.
After your application accepts secure connections, the next step is to configure the quarkus.http.ssl.certificate.trust-store-file
property with the name of the file that holds all the certificates your application trusts. This file also includes information about how your application requests certificates when a client, such as a browser or another service, tries to access one of its protected resources.
Because JKS is no longer the default keystore and truststore format in Quarkus, the framework makes an educated guess based on the file extension:
-
.pem
,.crt
, and.key
are read as PEM certificates and keys. -
.jks
,.keystore
, and.truststore
are read as JKS keystores and truststores. -
.p12
,.pkcs12
, and.pfx
are read as PKCS12 keystores and truststores.
If your file does not use one of these extensions, you must set the format by using the following properties:
quarkus.http.ssl.certificate.key-store-file-type=JKS # or P12 or PEM quarkus.http.ssl.certificate.trust-store-file-type=JKS # or P12 or PEM
quarkus.http.ssl.certificate.key-store-file-type=JKS # or P12 or PEM
quarkus.http.ssl.certificate.trust-store-file-type=JKS # or P12 or PEM
JKS is becoming less commonly used. Since Java 9, the default keystore format in Java is PKCS12. The most significant difference between JKS and PKCS12 is that JKS is a format specific to Java. In contrast, PKCS12 is a standardized, language-neutral way of storing encrypted private keys and certificates.
Here is an example configuration for enabling mTLS:
quarkus.http.ssl.certificate.key-store-file=server-keystore.jks quarkus.http.ssl.certificate.key-store-password=the_key_store_secret quarkus.http.ssl.certificate.trust-store-file=server-truststore.jks quarkus.http.ssl.certificate.trust-store-password=the_trust_store_secret quarkus.http.ssl.client-auth=required quarkus.http.auth.permission.default.paths=/* quarkus.http.auth.permission.default.policy=authenticated quarkus.http.insecure-requests=disabled
quarkus.http.ssl.certificate.key-store-file=server-keystore.jks
quarkus.http.ssl.certificate.key-store-password=the_key_store_secret
quarkus.http.ssl.certificate.trust-store-file=server-truststore.jks
quarkus.http.ssl.certificate.trust-store-password=the_trust_store_secret
quarkus.http.ssl.client-auth=required
quarkus.http.auth.permission.default.paths=/*
quarkus.http.auth.permission.default.policy=authenticated
quarkus.http.insecure-requests=disabled
- 1
- The keystore where the server’s private key is located.
- 2
- The truststore from which the trusted certificates are loaded.
- 3
- Setting
quarkus.http.ssl.client-auth
torequired
makes the server demand client certificates. You can set it toREQUEST
if the server should accept requests without a certificate. This setting is useful when supporting multiple authentication methods besides mTLS. - 4
- Defines a policy where only authenticated users can access resources from your application.
- 5
- Disables the plain HTTP protocol, requiring all requests to use HTTPS. When you set
quarkus.http.ssl.client-auth
torequired
,quarkus.http.insecure-requests
is automatically disabled.
When an incoming request matches a valid certificate in the truststore, your application can obtain the subject by injecting a SecurityIdentity
as follows:
Obtaining the subject
@Inject SecurityIdentity identity; @GET @Produces(MediaType.TEXT_PLAIN) public String hello() { return String.format("Hello, %s", identity.getPrincipal().getName()); }
@Inject
SecurityIdentity identity;
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
return String.format("Hello, %s", identity.getPrincipal().getName());
}
You can also get the certificate by using the code outlined in the following example:
Obtaining the certificate
import java.security.cert.X509Certificate; import io.quarkus.security.credential.CertificateCredential; CertificateCredential credential = identity.getCredential(CertificateCredential.class); X509Certificate certificate = credential.getCertificate();
import java.security.cert.X509Certificate;
import io.quarkus.security.credential.CertificateCredential;
CertificateCredential credential = identity.getCredential(CertificateCredential.class);
X509Certificate certificate = credential.getCertificate();
2.2.3.1. Mapping certificate attributes to roles
The information from the client certificate can be used to add roles to Quarkus SecurityIdentity
.
You can add new roles to SecurityIdentity
after checking a client certificate’s common name (CN) attribute. The easiest way to add new roles is to use a certificate attribute to role mapping feature.
For example, you can update the properties shown in the section which introduces Mutual TLS authentication as follows:
quarkus.http.ssl.certificate.key-store-file=server-keystore.jks quarkus.http.ssl.certificate.key-store-password=the_key_store_secret quarkus.http.ssl.certificate.trust-store-file=server-truststore.jks quarkus.http.ssl.certificate.trust-store-password=the_trust_store_secret quarkus.http.ssl.client-auth=required quarkus.http.insecure-requests=disabled quarkus.http.auth.certificate-role-properties=cert-role-mappings.properties quarkus.http.auth.permission.certauthenticated.paths=/* quarkus.http.auth.permission.certauthenticated.policy=role-policy-cert quarkus.http.auth.policy.role-policy-cert.roles-allowed=user,admin
quarkus.http.ssl.certificate.key-store-file=server-keystore.jks
quarkus.http.ssl.certificate.key-store-password=the_key_store_secret
quarkus.http.ssl.certificate.trust-store-file=server-truststore.jks
quarkus.http.ssl.certificate.trust-store-password=the_trust_store_secret
quarkus.http.ssl.client-auth=required
quarkus.http.insecure-requests=disabled
quarkus.http.auth.certificate-role-properties=cert-role-mappings.properties
quarkus.http.auth.permission.certauthenticated.paths=/*
quarkus.http.auth.permission.certauthenticated.policy=role-policy-cert
quarkus.http.auth.policy.role-policy-cert.roles-allowed=user,admin
- 1
- The
cert-role-mappings.properties
classpath resource contains a map of certificate’s CN values to roles in the formCN=role
orCN=role1,role2
, etc. Let us assume it contains three entries:alice=user,admin
,bob=user
andjdoe=tester
. - 2 3 4
- Use HTTP security policy to require that
SecurityIdentity
must have eitheruser
oradmin
roles for the requests to be authorized.
Given the preceding configuration, the request is authorized if the client certificate’s CN attribute is equal to alice
or bob
and forbidden if it is equal to jdoe
.
2.2.3.2. Using certificate attributes to augment SecurityIdentity
You can always register SecurityIdentityAugmentor
if the automatic Mapping certificate attributes to roles option does not suit. Custom SecurityIdentityAugmentor
can check the values of different client certificate attributes and augment the SecurityIdentity
accordingly.
For more information about customizing SecurityIdentity
, see the Security identity customization section in the Quarkus "Security tips and tricks" guide.
2.3. Other supported authentication mechanisms
Quarkus Security also supports the following authentication mechanisms through extensions:
2.3.1. OpenID Connect authentication
OpenID Connect (OIDC) is an identity layer that works on top of the OAuth 2.0 protocol. OIDC enables client applications to verify the identity of a user based on the authentication performed by the OIDC provider and retrieve basic information about that user.
The Quarkus quarkus-oidc
extension provides a reactive, interoperable, multitenant-enabled OIDC adapter that supports Bearer token and Authorization Code Flow authentication mechanisms. The Bearer token authentication mechanism extracts the token from the HTTP Authorization header.
The Authorization Code Flow mechanism redirects the user to an OIDC provider to authenticate the user’s identity. After the user is redirected back to Quarkus, the mechanism completes the authentication process by exchanging the provided code that was granted for the ID, access, and refresh tokens.
You can verify ID and access JSON Web Token (JWT) tokens by using the refreshable JSON Web Key (JWK) set or introspect them remotely. However, opaque, also known as binary tokens, can only be introspected remotely.
Using the Quarkus OIDC extension, both the Bearer token and Authorization Code Flow authentication mechanisms use SmallRye JWT authentication to represent JWT tokens as MicroProfile JWT org.eclipse.microprofile.jwt.JsonWebToken
.
2.3.1.1. Additional Quarkus resources for OIDC authentication
For more information about OIDC authentication and authorization methods that you can use to secure your Quarkus applications, see the following resources:
OIDC topic | Quarkus information resource |
---|---|
Bearer token authentication mechanism | |
Authorization Code Flow authentication mechanism | |
OIDC and SAML Identity broker | OpenID Connect (OIDC) Authorization Code Flow and SAML Identity broker |
Multiple tenants that can support the Bearer token authentication or Authorization Code Flow mechanisms | |
Securing Quarkus with commonly used OpenID Connect providers | |
Using Keycloak to centralize authorization | Using OpenID Connect (OIDC) and Keycloak to centralize authorization |
To enable the Quarkus OIDC extension at runtime, set quarkus.oidc.tenant-enabled=false
at build time. Then, re-enable it at runtime by using a system property.
For more information about managing the individual tenant configurations in multitenant OIDC deployments, see the Disabling tenant configurations section in the "Using OpenID Connect (OIDC) multi-tenancy" guide.
2.3.1.2. OpenID Connect client and filters
The quarkus-oidc-client
extension provides OidcClient
for acquiring and refreshing access tokens from OpenID Connect and OAuth2 providers that support the following token grants:
-
client-credentials
-
password
-
refresh_token
The quarkus-resteasy-client-oidc-filter
extension requires the quarkus-oidc-client
extension. It provides JAX-RS RESTful Web Services OidcClientRequestFilter
, which sets the access token acquired by OidcClient
as the Bearer
scheme value of the HTTP Authorization
header. This filter can be registered with MicroProfile REST client implementations injected into the current Quarkus endpoint, but it is not related to the authentication requirements of this service endpoint. For example, it can be a public endpoint or be protected with mTLS.
In this scenario, you do not need to protect your Quarkus endpoint by using the Quarkus OpenID Connect adapter.
The quarkus-resteasy-client-oidc-token-propagation
extension requires the quarkus-oidc
extension. It provides Jakarta REST TokenCredentialRequestFilter
, which sets the OpenID Connect Bearer token or Authorization Code Flow access token as the Bearer
scheme value of the HTTP Authorization
header. This filter can be registered with MicroProfile REST client implementations injected into the current Quarkus endpoint, which must be protected by using the Quarkus OIDC adapter. This filter can propagate the access token to the downstream services.
For more information, see the OpenID Connect client and token propagation quickstart and OpenID Connect (OIDC) and OAuth2 client and filters reference guides.
2.3.2. SmallRye JWT authentication
The quarkus-smallrye-jwt
extension provides a MicroProfile JSON Web Token (JWT) 2.1 implementation and multiple options to verify signed and encrypted JWT
tokens. It represents them as org.eclipse.microprofile.jwt.JsonWebToken
.
quarkus-smallrye-jwt
is an alternative to the quarkus-oidc
Bearer token authentication mechanism and verifies only JWT
tokens by using either Privacy Enhanced Mail (PEM) keys or the refreshable JWK
key set. quarkus-smallrye-jwt
also provides the JWT generation API, which you can use to easily create signed
, inner-signed
, and encrypted
JWT
tokens.
For more information, see the Using JWT RBAC guide.
2.4. Choosing between OpenID Connect and SmallRye JWT authentication mechanisms
Use the following information to select the appropriate token authentication mechanism to secure your Quarkus applications.
List of authentication mechanism use cases
-
quarkus-oidc
requires an OpenID Connect provider such as Keycloak, which can verify the bearer tokens or authenticate the end users with the Authorization Code flow. In both cases,quarkus-oidc
requires a connection to the specified OpenID Connect provider. -
If the user authentication requires Authorization Code flow, or you need to support multiple tenants, use
quarkus-oidc
.quarkus-oidc
can also request user information by using both Authorization Code Flow and Bearer access tokens. -
If your bearer tokens must be verified, use
quarkus-oidc
orquarkus-smallrye-jwt
. -
If your bearer tokens are in a JSON web token (JWT) format, you can use any extensions in the preceding list. Both
quarkus-oidc
andquarkus-smallrye-jwt
support refreshing theJsonWebKey
(JWK) set when the OpenID Connect provider rotates the keys. Therefore, if remote token introspection must be avoided or is unsupported by the providers, usequarkus-oidc
orquarkus-smallrye-jwt
to verify JWT tokens. -
To introspect the JWT tokens remotely, you can use
quarkus-oidc
for verifying the opaque or binary tokens by using remote introspection.quarkus-smallrye-jwt
does not support the remote introspection of both opaque or JWT tokens but instead relies on the locally available keys that are usually retrieved from the OpenID Connect provider. -
quarkus-oidc
andquarkus-smallrye-jwt
support the JWT and opaque token injection into the endpoint code. Injected JWT tokens provide more information about the user. All extensions can have the tokens injected asPrincipal
. -
quarkus-smallrye-jwt
supports more key formats thanquarkus-oidc
.quarkus-oidc
uses only the JWK-formatted keys that are part of a JWK set, whereasquarkus-smallrye-jwt
supports PEM keys. -
quarkus-smallrye-jwt
handles locally signed, inner-signed-and-encrypted, and encrypted tokens. In contrast, althoughquarkus-oidc
can also verify such tokens, it treats them as opaque tokens and verifies them through remote introspection.
Architectural considerations drive your decision to use opaque or JSON web token (JWT) token format. Opaque tokens tend to be much shorter than JWT tokens but need most of the token-associated state to be maintained in the provider database. Opaque tokens are effectively database pointers.
JWT tokens are significantly longer than opaque tokens. Nonetheless, the providers effectively delegate most of the token-associated state to the client by storing it as the token claims and either signing or encrypting them.
Feature required | Authentication mechanism | |
---|---|---|
|
| |
Bearer JWT verification | Local verification or introspection | Local verification |
Bearer opaque token verification | Introspection | No |
Refreshing | Yes | Yes |
Represent token as | Yes | Yes |
Inject JWT as MP JWT | Yes | Yes |
Authorization code flow | Yes | No |
Multi-tenancy | Yes | No |
User information support | Yes | No |
PEM key format support | No | Yes |
SecretKey support | No | In JSON Web Key (JWK) format |
Inner-signed and encrypted or encrypted tokens | Introspection | Local verification |
Custom token verification | No | With injected JWT parser |
JWT as a cookie support | No | Yes |
2.5. Combining authentication mechanisms
If different sources provide the user credentials, you can combine authentication mechanisms. For example, you can combine the built-in Basic and the Quarkus quarkus-oidc
Bearer token authentication mechanisms.
The authentication process completes as soon as the first SecurityIdentity
is produced by one of the authentication mechanisms.
2.5.1. Inclusive Authentication
In some cases it can be required that all registered authentication mechanisms create their SecurityIdentity
. It can be required when the credentials such as tokens have to be passed over Mutual TLS authentication, for example, when users are authenticating via Virtual Private Network
, or when the current token has to be bound to the client certificate for the token verification to succeed, guaranteeing that the token was issued exactly to the same client which is currently passing this token to Quarkus alongside its client certificate.
In Quarkus such authentication is called inclusive
and you can enable it like this:
quarkus.http.auth.inclusive=true
quarkus.http.auth.inclusive=true
If the authentication is inclusive then SecurityIdentity
created by the first authentication mechanism can be injected into the application code. For example, if both Mutual TLS authentication and basic authentication mechanism authentications are required, the Mutual TLS authentication mechanism will create SecurityIdentity
first.
The Mutual TLS authentication mechanism has the highest priority when inclusive authentication is enabled, to ensure that an injected SecurityIdentity
always represents Mutual TLS authentication and can be used to get access to SecurityIdentity
identities provided by other authentication mechanisms.
Additional SecurityIdentity
instances can be accessed as a quarkus.security.identities
attribute on the first SecurityIdentity
, however, accessing these extra identities directly may not be necessary, for example, when both Mutual TLS authentication and OIDC Bearer authentication mechanisms have been combined and done their authentications, the authenticated bearer token can be injected as a token credential alongside SecurityIdentity
created by Mutual TLS authentication. This is exemplified below:
package org.acme.security; import io.quarkus.oidc.AccessTokenCredential; import io.quarkus.oidc.common.runtime.OidcConstants; import io.quarkus.security.identity.SecurityIdentity; import io.quarkus.vertx.http.runtime.security.HttpSecurityUtils; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; @ApplicationScoped public class InclusiveAuthExampleBean { @Inject SecurityIdentity mtlsIdentity; @Inject AccessTokenCredential accessTokenCredential; private AccessTokenCredential getAccessTokenCredential() { if (doItHardWay()) { var securityIdentities = HttpSecurityUtils.getSecurityIdentities(mtlsIdentity); if (securityIdentities != null) { SecurityIdentity bearerIdentity = securityIdentities.get(OidcConstants.BEARER_SCHEME); if (bearerIdentity != null) { return bearerIdentity.getCredential(AccessTokenCredential.class); } } } return accessTokenCredential; } }
package org.acme.security;
import io.quarkus.oidc.AccessTokenCredential;
import io.quarkus.oidc.common.runtime.OidcConstants;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.vertx.http.runtime.security.HttpSecurityUtils;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
@ApplicationScoped
public class InclusiveAuthExampleBean {
@Inject
SecurityIdentity mtlsIdentity;
@Inject
AccessTokenCredential accessTokenCredential;
private AccessTokenCredential getAccessTokenCredential() {
if (doItHardWay()) {
var securityIdentities = HttpSecurityUtils.getSecurityIdentities(mtlsIdentity);
if (securityIdentities != null) {
SecurityIdentity bearerIdentity = securityIdentities.get(OidcConstants.BEARER_SCHEME);
if (bearerIdentity != null) {
return bearerIdentity.getCredential(AccessTokenCredential.class);
}
}
}
return accessTokenCredential;
}
}
You cannot combine the Quarkus quarkus-oidc
Bearer token and smallrye-jwt
authentication mechanisms because both mechanisms attempt to verify the token extracted from the HTTP Bearer token authentication scheme.
2.5.2. Path-based authentication
2.5.2.1. Use HTTP Security Policy to enable path-based authentication
The following configuration example demonstrates how you can enforce a single selectable authentication mechanism for a given request path:
quarkus.http.auth.permission.basic-or-bearer.paths=/service quarkus.http.auth.permission.basic-or-bearer.policy=authenticated quarkus.http.auth.permission.basic.paths=/basic-only quarkus.http.auth.permission.basic.policy=authenticated quarkus.http.auth.permission.basic.auth-mechanism=basic quarkus.http.auth.permission.bearer.paths=/bearer-only quarkus.http.auth.permission.bearer.policy=authenticated quarkus.http.auth.permission.bearer.auth-mechanism=bearer
quarkus.http.auth.permission.basic-or-bearer.paths=/service
quarkus.http.auth.permission.basic-or-bearer.policy=authenticated
quarkus.http.auth.permission.basic.paths=/basic-only
quarkus.http.auth.permission.basic.policy=authenticated
quarkus.http.auth.permission.basic.auth-mechanism=basic
quarkus.http.auth.permission.bearer.paths=/bearer-only
quarkus.http.auth.permission.bearer.policy=authenticated
quarkus.http.auth.permission.bearer.auth-mechanism=bearer
Ensure that the value of the auth-mechanism
property matches the authentication scheme supported by HttpAuthenticationMechanism
, for example, basic
, bearer
, or form
.
2.5.2.2. Use annotations to enable path-based authentication for Jakarta REST endpoints
It is possible to use annotations to select an authentication mechanism specific to each Jakarta REST endpoint. This feature is only enabled when Proactive authentication is disabled due to the fact that the annotations can only be used to select authentication mechanisms after a REST endpoint has been matched. Here is how you can select an authentication mechanism per a REST endpoint basis:
quarkus.http.auth.proactive=false
quarkus.http.auth.proactive=false
import io.quarkus.oidc.AuthorizationCodeFlow; import io.quarkus.vertx.http.runtime.security.annotation.BasicAuthentication; import jakarta.annotation.security.RolesAllowed; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; @Path("hello") public class HelloResource { @GET @BasicAuthentication @Path("basic") public String basicAuthMechanism() { return "basic"; } @GET @RolesAllowed("admin") @AuthorizationCodeFlow @Path("code-flow") public String codeFlowAuthMechanism() { return "code-flow"; } }
import io.quarkus.oidc.AuthorizationCodeFlow;
import io.quarkus.vertx.http.runtime.security.annotation.BasicAuthentication;
import jakarta.annotation.security.RolesAllowed;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
@Path("hello")
public class HelloResource {
@GET
@BasicAuthentication
@Path("basic")
public String basicAuthMechanism() {
return "basic";
}
@GET
@RolesAllowed("admin")
@AuthorizationCodeFlow
@Path("code-flow")
public String codeFlowAuthMechanism() {
return "code-flow";
}
}
- 1
- The REST endpoint
/hello/basic
can only ever be accessed by using the Basic authentication. - 2
- This endpoint requires authentication, because when no standard security annotation accompanies the
@BasicAuthentication
annotation, the@Authenticated
annotation is added by default. - 3
- The
@AuthorizationCodeFlow
annotation can be combined with any other standard security annotation like@RolesAllowed
,@PermissionsAllowed
and others. - 4
- The REST endpoint
/hello/code-flow
can only ever be accessed by using the OIDC authorization code flow mechanism.
Authentication mechanism^ | Annotation |
---|---|
Basic authentication mechanism |
|
Form-based authentication mechanism |
|
Mutual TLS authentication mechanism |
|
Bearer token authentication mechanism |
|
OIDC authorization code flow mechanism |
|
SmallRye JWT authentication mechanism |
|
Quarkus automatically secures endpoints annotated with the authentication mechanism annotation. When no standard security annotation is present on the REST endpoint and resource, the io.quarkus.security.Authenticated
annotation is added for you.
It is also possible to use the io.quarkus.vertx.http.runtime.security.annotation.HttpAuthenticationMechanism
annotation to select any authentication mechanism based on its scheme. Annotation-based analogy to the quarkus.http.auth.permission.basic.auth-mechanism=custom
configuration property is the @HttpAuthenticationMechanism("custom")
annotation.
For consistency with various Jakarta EE specifications, it is recommended to always repeat annotations instead of relying on annotation inheritance.
2.5.2.3. Use inclusive authentication to enable path-based authentication
By default, Quarkus only supports Path-based authentication for one authentication mechanism per path. If more than one authentication mechanism must be used for the path-based authentication, you can write a custom HttpAuthenticationMechanism
as documented in the Dealing with more than one HttpAuthenticationMechanism section of the Security Tips and Tricks guide. Another option is to enable Inclusive Authentication in the lax mode and write a custom HttpSecurityPolicy
or PermissionChecker
that verifies that all registered HTTP authentication mechanisms created their mechanism-specific SecurityIdentity
.
Enable inclusive authentication in the lax mode
quarkus.http.auth.inclusive-mode=lax quarkus.http.auth.inclusive=true
quarkus.http.auth.inclusive-mode=lax
quarkus.http.auth.inclusive=true
- 1
- By default, inclusive authentication requires that all registered HTTP authentication mechanisms must create the
SecurityIdentity
. However, in the lax mode, the authentication succeeds if at least one registeredHttpAuthenticationMechanism
created theSecurityIdentity
.
Let’s assume that we have 3 registered mechanisms, mTLS, Basic and OIDC and you only require Basic and mTLS authentications to succeed to permit access to the hello
method. In this case, enabling an inclusive authentication in a lax mode allows to check which mechanisms produced the identity as shown in the example below:
Example of the HTTP Authentication mechanisms check
import io.quarkus.security.PermissionChecker; import io.quarkus.security.PermissionsAllowed; import io.quarkus.security.identity.SecurityIdentity; import io.quarkus.vertx.http.runtime.security.HttpSecurityUtils; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import java.util.Map; @Path("/hello") public class HelloResource { @PermissionsAllowed("mtls-basic") @GET public String hello() { return "Hello world"; } @PermissionChecker("mtls-basic") boolean isMtlsAndBasicAuthentication(SecurityIdentity identity) { Map<String, SecurityIdentity> identities = HttpSecurityUtils.getSecurityIdentities(identity); if (identities != null) { return identities.containsKey("basic") && identities.containsKey("x509"); } return false; } }
import io.quarkus.security.PermissionChecker;
import io.quarkus.security.PermissionsAllowed;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkus.vertx.http.runtime.security.HttpSecurityUtils;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import java.util.Map;
@Path("/hello")
public class HelloResource {
@PermissionsAllowed("mtls-basic")
@GET
public String hello() {
return "Hello world";
}
@PermissionChecker("mtls-basic")
boolean isMtlsAndBasicAuthentication(SecurityIdentity identity) {
Map<String, SecurityIdentity> identities = HttpSecurityUtils.getSecurityIdentities(identity);
if (identities != null) {
return identities.containsKey("basic") && identities.containsKey("x509");
}
return false;
}
}
- 1
- Permit access to the endpoint only if it is confirmed that both
mTLS
andBasic
authentication mechanisms have authenticated the current request.
2.5.2.4. How to combine it with HTTP Security Policy
The easiest way to define roles that are allowed to access individual resources is the @RolesAllowed
annotation. Nevertheless, it is also possible to use the HTTP Security Policy like in the example below:
quarkus.http.auth.policy.roles1.roles-allowed=user quarkus.http.auth.permission.roles1.paths=/hello/code-flow quarkus.http.auth.permission.roles1.applies-to=JAXRS quarkus.http.auth.permission.roles1.policy=roles1 quarkus.http.auth.permission.roles1.methods=GET
quarkus.http.auth.policy.roles1.roles-allowed=user
quarkus.http.auth.permission.roles1.paths=/hello/code-flow
quarkus.http.auth.permission.roles1.applies-to=JAXRS
quarkus.http.auth.permission.roles1.policy=roles1
quarkus.http.auth.permission.roles1.methods=GET
2.6. Proactive authentication
Proactive authentication is enabled in Quarkus by default. This means that if an incoming request has a credential, the request will always be authenticated, even if the target page does not require authentication. For more information, see the Quarkus Proactive authentication guide.
2.7. References
Chapter 3. Identity providers
In the Quarkus Security framework, identity providers play a crucial role in authentication and authorization by verifying user identities. IdentityProvider
creates a SecurityIdentity
instance, which gets used during user authentication to verify and authorize access requests to your Quarkus application.
IdentityProvider
converts the authentication credentials provided by HttpAuthenticationMechanism
to a SecurityIdentity
instance.
Some extensions, such as the ones for OIDC and SmallRye JWT, include inline IdentityProvider
implementations specific to the supported authentication flow. For example, quarkus-oidc
uses its own IdentityProvider
to convert a token to a SecurityIdentity
instance.
If you use Basic or form-based authentication, you must add an IdentityProvider
instance to convert a username and password to a SecurityIdentity
instance.
To get started with security in Quarkus, consider combining the Quarkus built-in Basic HTTP authentication with the Jakarta Persistence identity provider to enable role-based access control (RBAC).
For more information about Basic authentication, its mechanisms, and related identity providers, see the following resources:
Chapter 4. Proactive authentication
Learn how to manage proactive authentication in Quarkus, including customizing settings and handling exceptions. Gain practical insights and strategies for various application scenarios.
Proactive authentication is enabled in Quarkus by default. It ensures that all incoming requests with credentials are authenticated, even if the target page does not require authentication. As a result, requests with invalid credentials are rejected, even if the target page is public. Requests without credentials are not rejected, because anonymous requests are allowed.
You can turn off this default behavior if you want to authenticate only when the target page requires it. To turn off proactive authentication so that authentication occurs only when the target page requires it, modify the application.properties
configuration file as follows:
quarkus.http.auth.proactive=false
quarkus.http.auth.proactive=false
If you turn off proactive authentication, the authentication process runs only when an identity is requested. An identity can be requested because of security rules that require the user to authenticate or because programmatic access to the current identity is required.
If proactive authentication is not used, accessing SecurityIdentity
is a blocking operation. This is because authentication might have yet to happen, and accessing SecurityIdentity
might require calls to external systems, such as databases, that might block the operation. For blocking applications, this is not an issue. However, if you have disabled authentication in a reactive application, this fails because you cannot do blocking operations on the I/O thread. To work around this, you need to @Inject
an instance of io.quarkus.security.identity.CurrentIdentityAssociation
and call the Uni<SecurityIdentity> getDeferredIdentity();
method. Then, you can subscribe to the resulting Uni
to be notified when authentication is complete and the identity is available.
You can still access SecurityIdentity
synchronously with public SecurityIdentity getIdentity()
in Quarkus REST (formerly RESTEasy Reactive) from endpoints that are annotated with @RolesAllowed
, @Authenticated
, or with respective configuration authorization checks because authentication has already happened. The same is also valid for Reactive routes if a route response is synchronous.
When proactive authentication is disabled, standard security annotations used on CDI beans do not function on an I/O thread if a secured method that is not void synchronously returns a value. This limitation arises from the necessity for these methods to access SecurityIdentity
.
The following example defines HelloResource
and HelloService
. Any GET request to /hello
runs on the I/O thread and throws a BlockingOperationNotAllowedException
exception.
There is more than one way to fix the example:
-
Switch to a worker thread by annotating the
hello
endpoint with@Blocking
. -
Change the
sayHello
method return type by using a reactive or asynchronous data type. -
Move the
@RolesAllowed
annotation to the endpoint. This could be one of the safest ways because accessingSecurityIdentity
from endpoint methods is never the blocking operation.
import jakarta.annotation.security.PermitAll; import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import io.smallrye.mutiny.Uni; @Path("/hello") @PermitAll public class HelloResource { @Inject HelloService helloService; @GET public Uni<String> hello() { return Uni.createFrom().item(helloService.sayHello()); } }
import jakarta.annotation.security.PermitAll;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import io.smallrye.mutiny.Uni;
@Path("/hello")
@PermitAll
public class HelloResource {
@Inject
HelloService helloService;
@GET
public Uni<String> hello() {
return Uni.createFrom().item(helloService.sayHello());
}
}
import jakarta.annotation.security.RolesAllowed; import jakarta.enterprise.context.ApplicationScoped; @ApplicationScoped public class HelloService { @RolesAllowed("admin") public String sayHello() { return "Hello"; } }
import jakarta.annotation.security.RolesAllowed;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class HelloService {
@RolesAllowed("admin")
public String sayHello() {
return "Hello";
}
}
4.1. Activating the CDI request context
You may need to inject @RequestScoped
beans during authentication and authorization. A good example of this is accessing a database during a SecurityIdentity
augmentation, which is described in the Security Identity Customization section of the "Security Tips and Tricks" guide. If authentication or authorization fails with the jakarta.enterprise.context.ContextNotActiveException
, disabling proactive authentication is most often the best solution. Users can also activate CDI request context, for example, by using the @ActivateRequestContext
annotation. However, some CDI beans may not be ready for use.
One exception to this solution is a situation when application endpoints are secured with the Authorization using configuration. For more information, see the Inject RequestScoped beans into HttpSecurityPolicy section of the "Authorization of Web endpoints" guide for more information.
4.2. Customize authentication exception responses
You can use Jakarta REST ExceptionMapper
to capture Quarkus Security authentication exceptions such as io.quarkus.security.AuthenticationFailedException
. For example:
package io.quarkus.it.keycloak; import jakarta.annotation.Priority; import jakarta.ws.rs.Priorities; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriInfo; import jakarta.ws.rs.ext.ExceptionMapper; import jakarta.ws.rs.ext.Provider; import io.quarkus.security.AuthenticationFailedException; @Provider @Priority(Priorities.AUTHENTICATION) public class AuthenticationFailedExceptionMapper implements ExceptionMapper<AuthenticationFailedException> { @Context UriInfo uriInfo; @Override public Response toResponse(AuthenticationFailedException exception) { return Response.status(401).header("WWW-Authenticate", "Basic realm=\"Quarkus\"").build(); } }
package io.quarkus.it.keycloak;
import jakarta.annotation.Priority;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriInfo;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;
import io.quarkus.security.AuthenticationFailedException;
@Provider
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFailedExceptionMapper implements ExceptionMapper<AuthenticationFailedException> {
@Context
UriInfo uriInfo;
@Override
public Response toResponse(AuthenticationFailedException exception) {
return Response.status(401).header("WWW-Authenticate", "Basic realm=\"Quarkus\"").build();
}
}
Some HTTP authentication mechanisms must handle authentication exceptions themselves to create a correct authentication challenge. For example, io.quarkus.oidc.runtime.CodeAuthenticationMechanism
, which manages OpenID Connect (OIDC) authorization code flow authentication, must build a correct redirect URL and set a state cookie. Therefore, avoid using custom exception mappers to customize authentication exceptions thrown by such mechanisms. Instead, a safer approach is to ensure that proactive authentication is enabled and to use Vert.x HTTP route failure handlers. This is because events come to the handler with the correct response status and headers. Then, you must only customize the response; for example:
package io.quarkus.it.keycloak; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.event.Observes; import io.quarkus.security.AuthenticationFailedException; import io.vertx.core.Handler; import io.vertx.ext.web.Router; import io.vertx.ext.web.RoutingContext; @ApplicationScoped public class AuthenticationFailedExceptionHandler { public void init(@Observes Router router) { router.route().failureHandler(new Handler<RoutingContext>() { @Override public void handle(RoutingContext event) { if (event.failure() instanceof AuthenticationFailedException) { event.response().end("CUSTOMIZED_RESPONSE"); } else { event.next(); } } }); } }
package io.quarkus.it.keycloak;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import io.quarkus.security.AuthenticationFailedException;
import io.vertx.core.Handler;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
@ApplicationScoped
public class AuthenticationFailedExceptionHandler {
public void init(@Observes Router router) {
router.route().failureHandler(new Handler<RoutingContext>() {
@Override
public void handle(RoutingContext event) {
if (event.failure() instanceof AuthenticationFailedException) {
event.response().end("CUSTOMIZED_RESPONSE");
} else {
event.next();
}
}
});
}
}