Seems like you're asking multiple things here.
You can supply Koa app.keys = [...]
multiple keys for the purpose of key rotation.
For example, if you want to generate a new key every month, you can sign new cookies with it without immediately invalidating all old cookies. Instead, you'd prefer to let old cookies naturally expire.
If key rotation isn't something you care about, then you'd just just use app.keys = ['mysecret']
that you never change.
koa-csrf
's middleware actually does use the keys you set with app.keys=
.
Koa passes app.keys
into its Cookies instance (https://github.com/pillarjs/cookies) so that the built-in this.cookies.get()
and this.cookies.set()
will use the keys (if provided).
koa-session
uses Koa's built-in this.cookies.{get,set}
.
koa-csrf
uses koa-session
.
But all of that is irrelevant.
The 403 response isn't complaining that you haven't set app.keys=
secrets. It's complaining that you haven't provided a CSRF token (aka secret) much less a valid one.
Your "fix" of manually setting this.session.secret
is just manually setting the value where koa-csrf looks to find the CSRF token. You're bypassing the entire security measure of the CSRF system.
The whole point of a CSRF token system is to ensure that someone hitting a protected endpoint actually originated from, say, a <form>
that posts to that endpoint from a page that you control.
It does this by generating a token, saving it in a cookie, attaching the token to the form, and then, on submission, it ensures that the form token matches the session token.
What you seem to be missing is that you have to:
- Generate the CSRF token
- Set it to the
secret
cookie for the client
- Expose the CSRF token to the client so that they submit it to your protected endpoint.
- On the protected endpoint, ensure that the client sends a CSRF token that matches the CSRF token in their 'secret' cookie. Here are the places that
koa-csrf checks to find that token.
koa-csrf's this.csrf
call does #1 and #2. You have to implement #3. koa-csrf's this.assertCSRF
does #4.
So, all together, this is how it looks (untested):
var koa = require('koa')
var csrf = require('koa-csrf')
var session = require('koa-session')
var Router = require('koa-router');
var bodyParser = require('koa-bodyparser');
var app = koa()
app.keys = ['session secret']
app.use(session())
app.use(bodyParser())
csrf(app)
app.use(csrf.middleware)
var router = new Router();
router.get('/messages', function*() {
this.render('new_message_form.html', {
token: this.csrf // this call also sets `this.session.secret` for you
});
});
router.post('/messages', function*() {
this.assertCSRF(this.request.body);
// If we get this far, then the CSRF check passed
yield database.insertMessage(this.body.message);
});
app.use(router.routes());
app.listen(3000, () => console.log('server listening on 3000'));
And here's what 'new_message_form.html' would look like. Notice that I'm setting a hidden field _csrf
so that when the user submits it, the token generated by this.csrf
is sent to my protected endpoint, and the _csrf
field is one of the places that koa-csrf checks to find the submitted token.
<form action="/messages" method="POST">
<input type="hidden" name="_csrf" value="{{ token }}">
<input type="message" name="message" placeholder="Write your message here...">
<button type="submit">Save Message<button>
</form>
koa-generic-session
notkoa-session
which is different. I suspect there's a miss communication betweenkoa-generic-session
andkoa-csrf
. By the way, thanks, I now understand the purpose of that keys array. – Boarish