Sorry, I don't quite understand how secret key works in koa. In koa, there is
a keys
field on app
object which will be used like this:
const app = new Koa();
app.keys = ['some secret', 'another secret', 'or more ...']; // it's an
// array right?
And then, when using koa-csrf
middleware, by default the built-in csrf.middleware
doesn't use the keys provided by app.keys
. If I use
the default middleware, I need to create another middleware which set the
secret key on the session.
app.use(session()); // koa-generic-session
app.use(async (ctx, next) => { // without this custom middleware
ctx.session.secret = 'yet another secret'; // POST /protected-route
await next(); // will give 403
}); // missing secret
csrf(app);
app.use(csrf.middleware);
When I use Flask, I need to supply only one secret key which is
set by string
not an array
. Why need more than one secret key? Is using only one for the whole application not enough?
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:
secret
cookie for the clientkoa-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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With