Request limiting for authentication #7322
Replies: 8 comments 5 replies
-
@jesstelford has suggested it..
|
Beta Was this translation helpful? Give feedback.
-
I'm not sure this should be a Keystone concern. Services like fail2ban do a good job of this because it is their main focus. Do we want to own that too? If they are difficult to configure or set-up for specific situations such as graphQL don't they need to improve support for this rather than us? I think the most we should do is provide a good set-up guide? Or allow someone to develop this as a contrib plugin. @JedWatson mentioning you here because I think you also have feels about what the scope of Keystone should be. |
Beta Was this translation helpful? Give feedback.
-
@MadeByMike Fail2ban is an option for VPS deployments. It isn't a general solution for other deployment environments like a PaaS (Heroku, etc.) or a functions-as-a-service offering, though. I think password rate limiting is expected to be in-app by those environments (how it gets installed/enabled is a totally different question). Here's some discussion (and bikeshedding) for Drupal (which makes rate limiting a core feature): An external plugin is used in Django. |
Beta Was this translation helpful? Give feedback.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
This comment has been hidden.
-
Are there some suggested ways to handle multiple failed login attempts with Keystone? |
Beta Was this translation helpful? Give feedback.
-
Authenticator, maybe, expected , |
Beta Was this translation helpful? Give feedback.
-
Just for reference, here is how I implemented it using a custom Apollo Server middleware, rate-limiter-flexible and graphql-armor (to prevent alias bruteforce attacks): import { ApolloArmor } from '@escape.tech/graphql-armor'
import { applyMiddleware } from 'graphql-middleware'
import { RateLimiterMemory } from 'rate-limiter-flexible'
const rateLimiter = new RateLimiterMemory({
points: 5, // Maximum number of failed attempts
duration: 15 * 60, // Per 15 minutes
blockDuration: 15 * 60 // Block for 15 minutes after exceeding points
})
const withBruteforceProtection = {
Mutation: {
authenticateUserWithPassword: async (resolve, root, args, context, info) => {
const ip = context.req.ip
const key = `${ip}`
try {
// Consume 1 point for this attempt
await rateLimiter.consume(key)
const result = await resolve(root, args, context, info)
// If authentication succeeded, reset the rate limiter for this key
if (result?.sessionToken) {
await rateLimiter.delete(key)
}
return result
} catch (err) {
if (err instanceof Error) {
throw err
} else {
throw new Error('Too many failed attempts. Please try again later.')
}
}
}
}
}
export default withAuth(
config({
// ....
graphql: {
apolloConfig: {
...armor.protect()
},
extendGraphqlSchema: (schema) => {
return applyMiddleware(schema, withBruteforceProtection)
}
}
})
) For the sake of simplicity, it's using the in-memory RateLimiter, which could be changed to |
Beta Was this translation helpful? Give feedback.
-
There's no obvious/good/easy way right to prevent attackers from hammering an a Keystone app attempting to brute force a login. The use of bcrypt for password offers very slight protection (in so far as, if you're getting bruteforced, you'll hopefully notice a massive spike in load) but there's nothing to actually block/throttle requests.
Solution developers can deploy tools like fail2ban in front of their app servers but, given the nature of GraphQL, it's difficult to target specific queries/mutations. Solutions like this also complicate deployments and aren't workable for some platforms (Heroku, Now, etc.).
We should be secure by default and getting a basic fix for this in place, with reasonable defaults, shouldn't be too hard.
Beta Was this translation helpful? Give feedback.
All reactions