Proactive authentication
Proactive authentication is enabled in Quarkus by default. This means that if an incoming request has a credential then that request will always be authenticated, even if the target page does not require authentication.
Requests with an invalid credential will always be rejected, even when the page is public.
If you only want to authenticate when the target page requires authentication, you can change the default behavior.
To disable proactive authentication in Quarkus, set the following attribute
in the application.properties
configuration file:
quarkus.http.auth.proactive=false
If you disable 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 in use, 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 will fail 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
and will be notified when authentication is
complete and the identity is available.
You can still access |
Standard
security annotations on CDI beans are not supported on an I/O thread if a
non-void secured method returns a value synchronously and proactive
authentication is disabled because they need to access SecurityIdentity
.
In the following example, HelloResource
and HelloService
are defined.
Any GET request to /hello
will run on the I/O thread and throw 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
@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.RolesAllowed;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class HelloService {
@RolesAllowed("admin")
public String sayHello() {
return "Hello";
}
}
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.Response;
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 need to 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, needs to build a
correct redirect URL, cookies, and so on. For that reason, 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. You then only need to customize the response, as shown in the
following 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();
}
}
});
}
}