Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Playframework with CSRF : "CSRF token not found in session"?

I'm making a simple authentication system using Playframework with their built-in CSRF filter and Security.Authenticator system, but I'm facing a problem :

When the user fill his login/password and submit enter, I have the following error :

CSRF token not found in session

I checked my form and the CSRF token is really present and correctly placed (inside the tag)

Here is my routes :

GET     /login                      controllers.Authentication.login
POST    /login                      controllers.Authentication.authenticate

And my Authentication.java class :

@play.filters.csrf.AddCSRFToken
public static Result login() {
    if (Session.getAccount() != null) {
        return redirect(controllers.routes.Instances.list());
    }

    LoginForm loginForm = new LoginForm();
    if (ctx().session().containsKey("email")) {
        loginForm.setEmail(ctx().session().get("email"));
    }

    if (request().queryString().containsKey("disconnected")) {
        flash("warning", Messages.get("secure.logout")); // TODO : Warning flash message
    }

    Form<LoginForm> form = Form.form(LoginForm.class).fill(loginForm);
    return ok(views.html.login.render(form));
}

@play.filters.csrf.AddCSRFToken
@play.filters.csrf.RequireCSRFCheck
public static Result authenticate() {
    Form<LoginForm> form = Form.form(LoginForm.class).bindFromRequest();
    if (form.hasErrors()) {
        return badRequest(views.html.login.render(form));
    }

    LoginForm login = form.get();
    Account account = Account.findByEmail(login.getEmail());

    if (account == null || !account.checkPassword(login.getPassword())) {
        form.reject("email", "secure.invalid.login");
        return badRequest(views.html.login.render(form));
    }

    String next = null;
    if (ctx().session().containsKey("next")) {
        next = ctx().session().get("next");
        if ("".equals(next)) {
            next = null;
        }
    }

    session().clear();
    session("email", login.getEmail());

    if (next != null) {
        return redirect(next);
    } else {
        return redirect(controllers.routes.Instances.list());
    }
}

The properties I setted in the application.conf :

csrf.token.name="csrf"
csrf.sign.tokens=true

Here's my form (to show that the token is correctly positionned) :

<form action="@controllers.routes.Authentication.authenticate" method="post" class="form-vertical" role="form">
    <fieldset>
        @defining(form("email")) { element =>
        <div class="form-group fade-in two@if(element.hasErrors){ has-error}">
            <label class="control-label" for="[email protected]">Email:</label>
            <input type="email" name="@element.name" id="[email protected]" class="form-control" value="@element.value" placeholder="Enter your email" required />
            @element.errors.map { error => <span class="help-block">@play.i18n.Messages.get(error.message)</span>}
        </div>}
        @defining(form("password")) { element =>
        <div class="form-group fade-in three@if(element.hasErrors){ has-error}">
            <label class="control-label" for="[email protected]">Password:</label>
            <input type="password" name="@element.name" id="[email protected]" class="form-control" value="@element.value" placeholder="Enter your password" required />
            @element.errors.map { error => <span class="help-block">@play.i18n.Messages.get(error.message)</span>}
        </div>}
        <div class="form-group actions fade-in four">
            <div class="text-right">
                <input type="submit" name="save" value="Sign me in" class="btn btn-primary" />
                @helper.CSRF.formField
            </div>
        </div>
    </fieldset>
</form>

Update 1: I removed all the @addCSRFToken and @requireCSRFToken and defined a global CSRF token instead, per @rhj recommandation. Now I got this error instead :

Invalid token found in form body

But as you can see in the HTML Form, the token is placed inside the form and should be identified!

Update 2: What bothers me the most, is that this problem only occurs in the Login page, and not elsewhere! Is there some session to enable first? I guess not but maybe ?!

Update 3: More odd, I just found out that if I change the value of csrf.token.name and reload the page, it works. Then if I log out, open a new page and try to logs again, it fails with the message Invalid token found in form body. If I again change the value of csrf.token.name and do it again, it will works again.

Update 4: I narrowed down the problem. I also use the play.mvc.Security.Authenticator and it seems that the token verification fails ONLY when I display the page after a redirect(). If I go straight to the login page, I won't have the error message displayed.

Final update! : I finally found the problem, it was in another class that I wasn't suspecting, that was called and cleared the session, making the token useless!

like image 504
Cyril N. Avatar asked Oct 01 '14 07:10

Cyril N.


2 Answers

Finally the problem was located in an other part of my code that was cleaning the session in the login() method, removing the csrf token with it ...

like image 167
Cyril N. Avatar answered Oct 19 '22 01:10

Cyril N.


Add the below line in Global.java

@Override
public <T extends EssentialFilter> Class<T>[] filters() {
    return new Class[]{CSRFFilter.class};
}

For this you have to import

import play.api.mvc.EssentialFilter;
import play.filters.csrf.CSRFFilter;

Hope this will help you

like image 24
rkj Avatar answered Oct 19 '22 01:10

rkj