Search code examples
resteasyquarkusquarkus-qute

Quarkus provide redirect url if not authenticated


I am building out a web ui, and decided to get auth through jwt in a cookie.

I have gotten that bare minimum to work, but when I keep a page behind a role, the page (predictably) doesn't load. Rather than not loading, I would prefer to be able to tell the endpoint to redirect to the actual login page. Is this possible? Ideally I would be able to supply an annotation on the endpoint to say "go here if not auth'ed".

Code where I am setting up the web ui endpoints:

@Traced
@Slf4j
@Path("/")
@Tags({@Tag(name = "UI")})
@RequestScoped
@Produces(MediaType.TEXT_HTML)
public class Index extends UiProvider {
    
    @Inject
    @Location("webui/pages/index")
    Template index;
    @Inject
    @Location("webui/pages/overview")
    Template overview;

    @Inject
    UserService userService;

    @Inject
    JsonWebToken jwt;

    @GET
    @PermitAll
    @Produces(MediaType.TEXT_HTML)
    public TemplateInstance index(
            @Context SecurityContext securityContext
    ) {
        logRequestContext(jwt, securityContext);
        return index.instance();
    }

    @GET
    @Path("overview")
    @RolesAllowed("user")
    @Produces(MediaType.TEXT_HTML)
    public TemplateInstance overview(
            @Context SecurityContext securityContext
    ) {
        logRequestContext(jwt, securityContext);
        return overview.instance();
    }
}

Solution

  • This depends on how you want to handle the security and navigation. If you use declarative RBAC(@RolesAllowed) than you can only control the response via JAXRS ExceptionMapper. You need to know the exception, that is thrown when the user does not have access to the required resources; for example in case user has valid token but does not satisfy @RolesAllowed constrain, then it would be io.quarkus.security.ForbiddenException, if he did not pass valid credentials, then io.quarkus.security.UnauthorizedException.

    When you know the exception, you can define your ExceptionMapper(make sure you set the correct priority via @Priority as there might be other exception mappers on the classpath, e.g. those that Quarkus provides in dev mode).

    The actual response handling depends on what the client is: if it is browser, then you probably want to return 303 or 302 redirect response with the location header containing your login URL so that the original request is redirected to login. An example for UnauthorizedException:

    import io.quarkus.security.UnauthorizedException;
    
    import javax.annotation.Priority;
    import javax.ws.rs.core.*;
    import javax.ws.rs.ext.ExceptionMapper;
    import javax.ws.rs.ext.Provider;
    
    @Provider//register it as jaxrs provider
    @Priority(1)//lower number-higher prio
    //exception mappers are selected on best match for the exception class
    //so make sure you find the particular exception you want to map
    public class MyUnauthorizedExceptionMapper implements ExceptionMapper<UnauthorizedException> { 
        @Context//you can inject JAXRS contextual resources here
        UriInfo crc;
    
        @Override
        public Response toResponse(UnauthorizedException exception) {
            System.out.println("User not authorized to access: " + crc.getRequestUri());
            return Response //seeOther = 303 redirect
                    .seeOther(UriBuilder.fromUri("/someLogin")
                            //common pattern is to pass the original URL as param,
                            //so that after successful login, you redirect user back where he wanted
                            .queryParam("returnUrl", crc.getRequestUri())
                            .build())//build the URL where you want to redirect
                    .entity("Not authorized")//entity is not required
                    .build();
        }
    }
    

    Other option is, that you handle the authentication programmatically inside your endpoint, checking the Principal or current JWTToken for required attributes and, if not sufficient, return the corresponding Response e.g. return Response.seeOther("/login").

    This should work, but may cause issues if your client is for example some javascript code, doing POST requests. In general, you might be better off serving your UI from a dedicated server, using Quarkus as API endpoint provider, communicating via HTTP/REST between your UI and BE. That way you have way more control over the behavior.