CSRF protection for put post delete
Like in play1 or in django: csrf https://docs.djangoproject.com/en/dev/ref/contrib/csrf/#how-csrf-works
=> a form must have a token that can be validated
Some more information copied from google groups: https://groups.google.com/forum/#!topic/ninja-framework/KU4Hr6_3nZ8
The basics are already implemented:
- Ninja session cookie can generate a unique id.
- Don't allow javascript to read the session => That's the basic line of defense...
What is missing is:
- A simple method to include the unique id into a form (if it's a form request for instance) => A tag or something to include into Freemarker rendering.
- A filter to check whether the post request is valid. If the request parameter of the form is identical to the id in the cookie the request is not forged.
In one of our projects I wrote some custom code to do that, but we should include that into the framework. As I said above I'll attach some code to the issue.
Some code:
AuthenticityFilter: public class AuthenticityFilter implements Filter {
public static final String AUTHENTICITY_TOKEN = "authenticityToken";
@Override
public Result filter(FilterChain filterChain, Context context) {
Session session = context.getSession();
String authenticityToken = context.getParameter(AUTHENTICITY_TOKEN);
if (session.getAuthenticityToken().equals(authenticityToken)) {
return filterChain.next(context);
} else {
return Results
.forbidden()
.template(NinjaConstant.LOCATION_VIEW_FTL_HTML_FORBIDDEN);
}
}
}
The filter assumes your request contains a parameter called "authenticityToken". Authenticity token is generated and stored in the session via session.getAuthenticityToken() (already implemented).
But you have to include the parameter either via a query or post parameter right now..
How to display the token in the view ? now I'm using ${session.___AT} it it the right way ?
example :
<a href="${reverseRoute("controllers.DashboardController","logout","xsrf",session.___AT)}">logout</a>
hmm... in theory yes. But I think that's imperfect. getAuthenticityToken creates a new token when it's absent. session.___AT (in your view) does not do that.
So if you just include ${session.___AT} in your views you have to make sure that somewhere before in your controller session.getAuthenticityToken() has been called. Otherwise your view.ft.html will complain :)
As I write above: "Missing: A simple method to include the unique id into a form (if it's a form request for instance) => A tag or something to include into Freemarker rendering."
If you want to contribute let us know!
Yes I noticed this issue, so what I'm doing is calling session.getAuthenticityToken upon user login or session generation.
I'm newbi with freemarker, why can't I call session.getAuthenticityToken from the template ?
That's because it's implemented as map and not as object.
Have a look at: https://github.com/ninjaframework/ninja/blob/develop/ninja-core/src/main/java/ninja/template/TemplateEngineFreemarker.java
lin 239: if (!context.getSession().isEmpty()) { map.put("session", context.getSession().getData()); }
That puts the map of session into your Freemarker template. Therefore you cannot access getAuthenticity token.
There may be better ways to do this - contributions are welcome :)
I added this to my project, looks good!
@raphaelbauer Could a freemarker variable be set up to contain the token automatically? Then you just need something like
in your form, mark the controller method as FilterWith and you are done.
I wonder if POST methods should be filtered this way automatically though. That way, you are secure by default. I suppose, a controller can always be annotated with @NoXSRFProtect or equivalent if need be.
Of course github ate my markup instead of escaping it .... sigh.
The missing bit was a hidden HTML input field like [ input type="hidden" name="atoken" value="${atoken}" ]
Yes... totally agree - and that's really missing.
I guess there should be
- a helper that generates input type="hidden" name="atoken" value="${atoken}" ]
- A simple way to get hold of the session authenticity token. Best would be to inject the current session into the freemarker template. Then you can call session.getAuthenticityToken.
Are you interested in implementing something? We can certainly help :)
Maybe! My project is (a) really small and (b) behind where I should be :) so, not sure if I'll have time given that I probably only have two or three forms to XSRF protect. But I like Ninja so will dig in and see if I can make a patch in the coming weeks.
Cool... Let us know if we can help :)
Any update on this issue?
What about just passing the authenticity token to the template like e.g. prettime.
In TemplateFreemarkerEngine one could just add
map.put("token", context.getSession().getAuthenticityToken());
and in the template just call
${token}
Thats what I just tested and it worked. Of course, this has to be done in a cleaner with, returning the complete form.