9

It's a common best practice to renew the HTTP session when logging in a user. This will force a new session ID, avoiding session fixation vulnerabilities.

Is there a preferred pattern for implementing this with CDI when @SessionScoped beans are involved? The difficulty is that by invalidating the current HTTP session, you'll then get a different session-scoped bean with the next request, but not until the next request.

For example, assume a session bean for storing user login information:

@Named("sessionbean")
@SessionScoped
public class SessionBean implements Serializable {
    private int userId;
    private String username;
    private List<String> privileges;

    // Accessors omitted 
}

And another bean for managing the login:

@Named("loginbean")
@ViewScoped
public class LoginBean implements Serializable {

    private String username;
    private String password;
    @Inject private SessionBean session;
    @Inject private SessionManager sessionManager;
    @Inject private PrivilegeManager privilegeManager;      

    public String doLogin() {
        String destinationUrl;

        if (validate(username, password)) {
            FacesContext context = FacesContext.getCurrentInstance();

            // force renewal of HTTP session
            context.getExternalContext().invalidateSession();

            // retrieve new session bean  ** No longer works with CDI **
            Application app = context.getApplication();
            session = app.evaluateExpressionGet(context, "#{sessionbean}", SessionBean.class);

            session.setUsername(username);
            session.setSessionId(sessionManager.createNewSession(username));
            session.setPrivileges(privilegeManager.getPrivileges(username));

            destinationUrl = createLandingPageUrl();

        } else {
            destinationUrl = createFailureUrl("Unknown user or password");
        }

        return destinationUrl;
    }
}

With Managed Beans this would retrieve a new SessionBean, but with CDI, the code above would just return the same SessionBean. Any recommendations or clever ideas?

Didjit
  • 785
  • 2
  • 8
  • 26
  • I have no solution and I'm not sure if this is real best practice, looks somewhat awkwad to me. Shouldn't this be done by the container or e.g. by servlet filters? Nevertheless I upvoted because this questions seemes clear, asked well and a solid answer on SO would probably help others encountering the same challenge. – Selaron Jan 09 '20 at 16:03
  • I was a little surprised as well about the approach until I tried reading up on [Session fixation](https://www.owasp.org/index.php/Session_fixation). I'll have to think about this a little more since there seems to be something weird about your code which I cannot grasp. I might have worked by 'chance' when using JSF managed beans (with cdi btw they are still managed beans, just managed by some other container. ).I'll get back to thls later (unless BalusC kicks in) – Kukeltje Jan 09 '20 at 17:05
  • Can you create a [mcve]? No `SessionManager`, no `PrivilegeManager` and a simple xhtml page? – Kukeltje Jan 09 '20 at 20:02
  • @Kukeltje, working on it. I want to do my due diligence before just throwing it out there. – Didjit Jan 09 '20 at 23:39
  • Might not be needed. I just reproduced and what is in the link below (on the answer) is what is happening. It is invalidated on the **next request**. Out of curiosity I spend some time on a solution already or rather investigating why we never ran into this issue and why I read so little about it. And personally I think the 'invalidate session when logging in' is _a_ solution, but not the only (and maybe not the best) solution. I have one that works with creating a new session but I dislike that since it e.g. empties a 'basket' that was populated before logging in – Kukeltje Jan 10 '20 at 00:12
  • 1
    Why it is not a security issue in our case (I think) is that we, on logging in, set an additional cookie (hashed token). This token is also stored in the server in the session information and is in a servlet filter checked with each request. So even if the attacker has 'session fixation' (in whatever form) he/she/... cannot access the pages since the authentication cookie is missing there. Like in the image in https://www.owasp.org/index.php/Session_fixation, the response to '5' contains this cookie and that is not known to atacker, so 6 without this cookie will fail. – Kukeltje Jan 10 '20 at 00:18
  • I'll see if I can write an answer over the weekend with both the above in there and a solution that does create a new session in two steps when the user wants to (re) login. Since it can be solved in a not to complicated way – Kukeltje Jan 10 '20 at 00:20
  • Sorry, wrong formulation. It is invalidated on this request (session becomes null) but it still retrieves objects from the session that was associated with the current request wjen it started. A new object is retrieved on the next request. – Kukeltje Jan 10 '20 at 07:08
  • Thanks for the additional research, @Kukeltje. Very much appreciated. That's a great point about the second token. The session ID change solves the immediate problem. My only concern now is to ensure the session "basket" is cleared after logout/timeout. – Didjit Jan 10 '20 at 15:31
  • @Dijit: Since we do not change the session id, we do not run into this.. I'll leave our way as it is for now. Cheers – Kukeltje Jan 13 '20 at 09:33

2 Answers2

12

The difficulty is that by invalidating the current HTTP session, you'll then get a different session-scoped bean with the next request, but not until the next request.

Then don't invalidate the session, but change the session ID. In other words, don't use HttpSession#invalidate(), but use HttpServletRequest#changeSessionId() (new since Servlet 3.1, which you should undoubtedly already be using given that you're using JSF 2.3).

In code, replace

// force renewal of HTTP session object
context.getExternalContext().invalidateSession();

by

// force renewal of HTTP session ID
((HttpServletRequest) context.getExternalContext().getRequest()).changeSessionId();

This basically changes the JSESSIONID cookie without changing the HttpSession. It's perfect for session fixation prevention.

Explicitly invalidating the session is usually only useful during logout.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
0

I'm going to restrict this answer to be solely about CDI since I am not a security expert. I also don't know whether the general thing being asked for is a good idea or not. Regardless, here is how I think you would do what you're asking for.

Expressed in purely CDI terms, the question can be rephrased like:

I have an object that I know came from a particular Context. I know the lifecycle of objects produced by this Context. How can I properly tell the Context to invalidate the current object that it is managing, and load or create a new one?

The general approach is going to be:

  • @Inject a Provider<SessionBean> instead of SessionBean directly (this will let you ask CDI for the "new" object properly)
  • @Inject a BeanManager (so you can get the right Context that manages SessionScoped objects)
  • ask the BeanManager to give you the AlterableContext corresponding to the SessionScoped annotation
  • tell the AlterableContext to destroy the current bean's contextual instance
  • call Provider.get() to cause a new one to be created

So the relevant parts of your doLogin method might look like this (untested):

final AlterableContext context = (AlterableContext) this.beanManager.getContext(SessionScoped.class);
assert context != null;

final Bean<?> bean = beanManager.resolve(beanManager.getBeans(SessionBean.class));
assert bean != null;

context.destroy(bean);

final SessionBean newSessionBean = this.sessionBeanProvider.get();
assert newSessionBean != null;

I think that should work.

Laird Nelson
  • 15,321
  • 19
  • 73
  • 127
  • Thanks for the suggestion. Unfortunately it doesn't seem to work. I found that `sessionBeanProvider` returns the same bean. Even the `BeanManager` returns the same `Bean<>` object despite the call to destroy it. Regardless, once the HTTP session is invalidated, the next request will have a new session and a different `SessionBean`. As I understand, CDI caches session-scoped objects during the JSF cycle. So perhaps the `Bean<>` doesn't actually get destroyed until the end of the cycle? – Didjit Jan 09 '20 at 20:07
  • @Didjit: Yep, that is what I investigated too: https://stackoverflow.com/questions/10350657/invalidating-session-with-cdijsf-not-working. Next request has a new session and does a new `@PostConstruct`. – Kukeltje Jan 09 '20 at 22:39