Dinesh kumar S
Dinesh kumar S

Reputation: 1

Single Session Sign-In Using Quarkus: Implementing Single-Session-at-a-Time with quarkus-hibernate-reactive

I'm implementing a Single Session login feature using Quarkus. The process involves a /login API where, upon successful login, a JWT token is generated and returned in the response. The token is also saved in the database.

After that, whenever I call any other API, I need to validate the JWT token by checking it against the stored tokens in the database. However, I'm facing issues when using quarkus-hibernate-reactive, and I encounter the error:

"Session/EntityManager is closed"

Below is the code I'm using to validate the token for single session login. Could you please provide guidance on how to properly implement single session login in a reactive Quarkus environment, along with any references that might help?

package asc.qrs.common;

import asc.qrs.repository.UserSessionRepository;
import io.quarkus.logging.Log;
import io.smallrye.mutiny.Uni;
import jakarta.annotation.Priority;
import jakarta.inject.Inject;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.Provider;

@Provider
@Priority(1)
public class SingleSessionFilter implements ContainerRequestFilter {

    @Inject
    UserSessionRepository userSessionRepository;

    @Override
    public void filter(ContainerRequestContext requestContext) {
        String path = requestContext.getUriInfo().getPath();

        if (path.equals(AppConstant.USER_LOGIN)) {
            return; // Skip token validation for the login endpoint
        }

        Log.info("SingleSessionFilter started");

        String header = requestContext.getHeaderString("Authorization");

        if (header != null && header.startsWith("Bearer ")) {
            String token = header.substring(7);

            Log.infof("Extracted token: %s", token);

            // Use reactive validation of the token
            checkTokenValidity(token)
                    .subscribe().with(
                            isValid -> {
                                if (!isValid) {
                                    Log.infof("Invalid token detected: %s", token);
                                    abortRequest(requestContext, Response.Status.UNAUTHORIZED, "Invalid token");
                                } else {
                                    Log.infof("Token is valid: %s", token);
                                }
                            },
                            failure -> {
                                Log.errorf("Error during token validation: %s", failure.getMessage());
                                abortRequest(requestContext, Response.Status.INTERNAL_SERVER_ERROR, "Internal error occurred");
                            }
                    );
        } else {
            Log.info("Authorization header missing or invalid");
            abortRequest(requestContext, Response.Status.UNAUTHORIZED, "Authorization header is missing or invalid");
        }
    }

    private Uni<Boolean> checkTokenValidity(String token) {
        return userSessionRepository.countByToken(token)
                .onItem().transform(count -> count > 0);
    }

    private void abortRequest(ContainerRequestContext requestContext, Response.Status status, String message) {
        requestContext.abortWith(
                Response.status(status).entity(message).build()
        );
    }
}

Upvotes: 0

Views: 42

Answers (1)

Michal Vavř&#237;k
Michal Vavř&#237;k

Reputation: 106

For reactive authorization check I'd suggest @PermissionChecker annotation, return Uni<Boolean> as shown here https://quarkus.io/guides/security-authorize-web-endpoints-reference#permission-checker. If you prefer using this filter, I don't think it is feasible to perform asynchronous check in a synchronous filter. If you are using Quarkus REST, use @ServerRequestFilter which has Uni support. The issue with current approach is that your request is being processed while you are still checking database, so even your endpoints could be invoked if your DB query took really long. And it could be related to the Hibernate Reactive issue or not, hard to say without a reproducer.

Upvotes: 1

Related Questions