How to redirect ajax request when session times out and form authentication is configured

Version 2

    Introduction

    It is a common requirement that web applications should be available only for authenticated users. These security settings are configured in web.xml by elements security-constraint, login-config and security-role and make use built in support of login modules. Another web.xml element session-timeout defines the validity of http sessions. When the session times out and a http request with unknown session id arrives then such request should be redirected to given (usually login) page. If you use the form login then you get this functionality out of the box.

    Problem

    Suppose a web application having form authentication method configured and is built on JSF2 facelets and makes use Richfaces 4.2 ajax controls. When session times out and an ajax requet arrives then empty ajax response is returned back. You have no indication that anything goes wrong and the current view is untouched. So how to handle ajax requests when session expires ?

    Solution

    I've tried a plenty of solutions described on several forums. Bellow I'm going to describe what works for me.

    Web application descriptor

    First configure web.xml descriptor to use faces servlet, session timeout, welcome facelet and form authentication:

     

    <web-app xmlns="http://java.sun.com/xml/ns/javaee"

             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

             xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"

             version="3.0">

     

        <servlet-mapping>

            <servlet-name>Faces Servlet</servlet-name>

            <url-pattern>/faces/*</url-pattern>

        </servlet-mapping>

     

        <session-config>

            <session-timeout>30</session-timeout>

        </session-config>

     

        <welcome-file-list>

            <welcome-file>faces/home.xhtml</welcome-file>

        </welcome-file-list>

     

        <login-config>

            <auth-method>FORM</auth-method>

            <realm-name>My realm</realm-name>

            <form-login-config>

                <form-login-page>/faces/login.xhtml</form-login-page>

                <form-error-page>/faces/login_error.xhtml</form-error-page>

            </form-login-config>

        </login-config>

    Faces configuration

    The JSF2 implementation fires phase events before and after each life cycle phase. These events are handled by phase listeners and you may implement your own and register it in faces-config.xml.

    Also note the navigation rule to home page.

     

    <faces-config xmlns="http://java.sun.com/xml/ns/javaee"

                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

                  xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_2_1.xsd"

                  version="2.1">

     

        <lifecycle>

            <phase-listener>com.project.admin.faces.SessionExpirationPhaseListener</phase-listener>

        </lifecycle>

     

        <navigation-rule>

            <from-view-id>*</from-view-id>

            <navigation-case>

                <from-outcome>home</from-outcome>

                <to-view-id>/home.xhtml</to-view-id>

                <redirect/>

            </navigation-case>

        </navigation-rule>

    Phase listener

    If the user is not authenticated you want to be notified very early, ideally before a view is rendered.

     

    public class SessionExpirationPhaseListener implements PhaseListener {

        @Override

        public PhaseId getPhaseId() {

            return PhaseId.RESTORE_VIEW;

        }

     

        @Override

        public void beforePhase(PhaseEvent event) {

        }

     

        @Override

        public void afterPhase(PhaseEvent event) {

            FacesContext context = FacesContext.getCurrentInstance();

     

            HttpServletRequest httpRequest = (HttpServletRequest) context.getExternalContext().getRequest();

            if (httpRequest.getRequestedSessionId() != null && !httpRequest.isRequestedSessionIdValid()) {

                String facesRequestHeader = httpRequest.getHeader("Faces-Request");

                boolean isAjaxRequest = facesRequestHeader != null && facesRequestHeader.equals("partial/ajax");

     

                // navigate to home page only for ajax requests

                if (isAjaxRequest) {

                    ConfigurableNavigationHandler handler = (ConfigurableNavigationHandler) context.getApplication().getNavigationHandler();

                    handler.performNavigation("home");

                }

            }

        }

     

    Redirecting to home page subsequently enforces the from authentication's redirect to login page.

    Last but not least the logic should be implemented in after phase, read BalusC comment why.