Session expired messages using Seam security
November 16, 2007
Differentiating between an initial login, a session expired, and a logout when displaying messages to the user on the login screen seems like a simple task. The implementation is unfortunately not quite as simple as it sounds and requires a little legwork. The following tutorial will guide you through the requirements.
The first component is a PhaseListener that allows you to determine whether the user’s session expired or whether this is the first time the user accessed the application. You have to make some assumptions here, but you can basically notify the user when the server session has ended by adding the following to a PhaseListener:
...
@Observer("org.jboss.seam.beforePhase")
public void beforePhase(PhaseEvent event)
{
if(event.getPhaseId() == PhaseId.RESTORE_VIEW)
{
HttpServletRequest request =
(HttpServletRequest) FacesContext.getCurrentInstance()
.getExternalContext().getRequest();
if(request.getRequestedSessionId() != null
&& request.getSession().isNew())
Events.instance().raiseEvent("security.sessionExpired");
...
Based on general cookie settings this will raise the event when the user still has the browser window open, the http session expired, and the user tries to access the application. If the user closes and reopens the browser to start the application, the event will not be raised. This of course makes the assumption that cookies expire when the browser session is ended (which is generally the case).
This works well, but unfortunately the FacesMessages component is not available until after the RENDER_RESPONSE phase has executed. This is because the FacesMessages component is conversation scoped and the conversation is not started until after the RENDER_RESPONSE phase. Thus, you have to have a component available to store your message until the FacesMessages component becomes available. This can be accomplished by using an appropriately scoped component. For example:
@Name("customAuthenticator")
@AutoCreate
@Scope(SESSION)
public class CustomAuthenticator implements Authenticator {
@Logger
private Log log;
...
private List<string> messages;
...
@Observer("security.sessionExpired")
public void sessionExpired(String message)
{
log.info("Adding session expired message...");
this.messages.add("User session expired.");
}
public void flushMessages()
{
for(String message : this.messages)
FacesMessages.instance().add(message);
this.messages.clear();
}
}
The flushMessages method should be invoked either by an action on your pages.xml or by flushing after the INVOKE_APPLICATION phase (this is up to your preference). Simply ensure that the flushing occurs only once the conversation has been initialized so that FacesMessages is available in the context.
This takes care of differentiating between a session timeout and a new session. So what about the logout? If you flush the security messages on every request, the user will end up seeing a message saying that the “User session ended” on logout.
To resolve this we can place a page in between and perform a meta redirect. First define a Seam page that does not require a login. Then in this page, add the following meta tags:
<meta http-equiv="Refresh" content="3; URL=/myApp/myPage.seam" />
This page can display a simple message to the user along the lines of: “You have successfully logged out.” The user will then be redirected to myPage after 3 seconds. As long as the messages are flushed on each request, the “User session ended” message will be long gone once the meta redirect executes.
Next add an entry in your pages.xml to redirect to this page when a logout occurs:
<page view-id="*">
<navigation from-action="#{identity.logout}">
<redirect view-id="/logoutConfirmation.xhtml" />
</navigation>
</page>
A feature request has been placed to include the security.sessionExpired event as well as a security.newSession event. If you are interested in Seam providing this behavior out-of-the-box, please vote!
Update: Are you looking for an advanced polling solution for session expiration? Christian Bauer blogged about an approach to polling for session expiration here.








Posted in 


content rss
November 19th, 2007 at 1:21 pm
Good morning Jacob,
Thanks a lot for the tutorial and it is great. However, the meta refresh solution is not very desirable to us because our application cannot redirect to another page without user’s explicit request.
I am in the middle of upgrading from Seam beta to 2.0.0.GA, so I will try the new org.jboss.seam.loggedOut event as you suggested. I will let you know the result.
Have a nice day!
Best regards,
Sheng
November 19th, 2007 at 6:13 pm
I look forward to hearing your approach. It’s always great to have alternatives
November 19th, 2007 at 7:13 pm
Good afternoon Jacob,
Sorry that I cannot make it work with the new org.jboss.seam.loggedOut event. So I followed your approach to make a third page in between. I used my home page, which does not require login, and clear the messages list there. In this way, I don’t need to redirect to the login page.
Thank you very much for all your help and have a nice day!
Best regards,
Sheng
November 27th, 2007 at 10:02 am
Really good and really interesting post. I expect (and other readers maybe :)) new useful posts from you!
Good luck and successes in blogging!
December 12th, 2007 at 4:28 pm
Hi,
your post is really helpful. I want to clarify one point.. Can you please elaborate what you mean by.. “you can basically notify the user when the server session has ended by adding the following to a PhaseListener”. Does this mean we need to modify seam source?
December 12th, 2007 at 5:29 pm
Hi Nayan,
You do not have to modify the seam source. It simply means that you have to keep in mind that the notification only works for general cookie settings as described in the next paragraph, i.e. cookies expire when the browser session is ended (browser is closed).
If the cookie is set to never expire or expiration occurs at some future date, the security.sessionExpired event will be triggered any time the session expires (regardless of whether the browser is closed and then reopened). Hope that clarifies your question.
March 17th, 2008 at 1:19 pm
Hello Jacob,
I am following your method. However, I could not make it work on the flushMessages() part. Because the HttpSession has expired, and CustomAuthenticator is a SESSION scope bean, when it’s the time FacesMessages is available, CustomAuthenticator is already re-constructed and this.messages is already gone.
Please explain how you do “The flushMessages method should be invoked either by an action on your pages.xml or by flushing after the INVOKE_APPLICATION phase (this is up to your preference).” Especially how to do it by an action on your pages.xml.
I invoke flushMessages() by
@Observer("org.jboss.seam.afterPhase")
public void flushMessages(PhaseEvent event) {
PhaseId id = event.getPhaseId();
if(id == PhaseId.INVOKE_APPLICATION){
for(FacesMessage message : this.faceMessages){
FacesMessages.instance().add(message);
}
faceMessages.clear();
}
}
Thank you very much.
March 25th, 2008 at 8:04 am
Hi Lijun,
Sorry for my delay.
The session expired event is fired after the context is reconstructed (it actually checks for the new session and is *not* fired when the session is destroyed). Thus, your CustomAuthenticator is part of the new session ensuring the message is available. Flushing the messages after the INVOKE_APPLICATION phase will only work if the INVOKE_APPLICATION phase is executed (which may be your issue). What you can do is configure an <action> in your pages.xml configuration for the login page which executes your flush. By the time the action executes the FacesMessages component is available from the context.
The best approach is using the patch submitted for JBSEAM-2257 as it avoids issues with the context not being available. The session expiration check is performed *after* the context has been initialized ensuring that the FacesMessages component is available when the event is fired. This is scheduled for inclusion in 2.1.0.GA.
Hope that helps.
Jacob