Local JWT Auth Implementation

Hi @3nvy and thanks for working through this solution.

I know I have made my position on rolling one’s own Authentication before – and it’s not something I advocate or do … but I do understand why there is a use case.

I just have to ask is that use case is worth the risk and the responsibilities one assumes as a an app developer and custodian of personal information and credentials.

And for many it might be. I just think anyone doing so needs to be aware of this and also do their earnest effort to safeguard that information.

To this end I have a few points:

1 - I like that you implemented the jwt-identify as a function – but what you have posted seems more of a lib abd not a Lambda function. If you see the RW function generator, it’s not really following that pattern.

That said, I really do think that the JWT auth “service” should be implemented as functions – but those should be secure and verified by some secret key.

The functions can verify, issue, revoke, refresh tokens etc. In fact it is not so different that what GoTrue sets out to do:

This also means that the token and user creation is done outside the GraphQL api and services.

2 - Please be aware that by having a model like:

model User {
  id            Int     @id @default(autoincrement())
  email         String  @unique
  username      String  @unique
  password      String
  refreshToken  String?
  createdAt     DateTime @default(now())
}

where the password and refreshToken is available (albeit the password is bcrypted) that because:

    users: [User!]!
    user(id: Int!): User
  }

and given:

export const users = () => {
  return db.user.findMany()
}

export const user = ({ id }) => {
  return db.user.findOne({
    where: { id },
  })
}

export const createUser = async ({ input }) => {
  const password = await bcrypt.hash(input.password, 12)
  const user = db.user.create({
    data: { ...input, password },
  })
  return user
}

export const updateUser = ({ id, input }) => {
  return db.user.update({
    data: input,
    where: { id },
  })
}

export const deleteUser = ({ id }) => {
  return db.user.delete({
    where: { id },
  })
}

… from what I can tell, none of these require authentication.

This means I can fetch your entire user list and their encrypted passwords.

In fact because UpdateUserInput and updateUser is open, I can change anyone’s password – especially because

 id            Int     @id @default(autoincrement())

you are using incremental integer ids.

So, update user 1, 2, 3, 4,5 etc.

I won’t go on because there’s some great pieces here and I think if this solution:

  • is re-arranged into a function-based API that follows the GoTrue interface service (auth, token management, profile management) that checks for and validates a secret token
  • don’t expose any user model data openly on the GraphQL api
  • perhaps have a separate Credentials model that uses the a uuid generated on user create but without a relation that keeps the User and the password/profile data related but not connected
  • Have a look at what Blitz has to do provide auth securely and here. It’s a lot of work.

This could be a great option for people who cannot use a third-party auth service.

Next:

My question to the redwood team is - does RW want to be in the Auth business or the Framework business. Building an auth service is hard and have to assess if efforts are well spent there.

Last:

I don’t meant to be the bearer (haha pun intended!) of bad news, but web apps cost money. Infrastructure costs money.

And I don’t work for Auth0 or anyone else, but I have been in the position to do a cost benefit and risk analysis of choosing a service or rolling one’s own and going with a service always wins. (And I know in certain countries some of these services are not an option – I get that. There is always a contrary case and that happens.)

Auth0 starts charging at 1,000+ monthly active users. And I think that is $23 US or $276 per year.

I’ve freelanced and hired developers and in the best case scenario that – for the year – is 2 hours of work or maybe 5 if you can pay someone $~50/hr. But even if you pay them $15/hr (which believe it or not is $3 more than most minimum wage jobs in the US). That’s 4 hours work for 5 days (or 1 week). And you might say, with this auth client I can get it up and running in an hour – and you’d be right. But - you don’t have: password change, email verification, rules pipeline, RBAC support a login/logout or any UI, etc.

So, for the cost of a handful or two hours of work – you have an Auth services than can service 1,000 active users per month. For. A. Year.

Yes, the cost goes up as the user base goes up – but … guess what … everything about web apps gets more expensive one you have to support more users.

So, no surprise here, but graphql in Redwood is a function – it’s a Lambda function running on AWS somewhere if you deploy to Netlify.

Netlify on it’s Free/Starter and Pro ($19/mo) plans give you 125,000 function invocations per month. At $99/mo Business (and Enterprise) you get unlimited.

So, let’s say you do have 1,000 active monthly users and we say that they use your app everyday except some weekends … so 25 days actively using the app (to make it simple).

1,000 users * 25 days = 25,000 user active days
125,000 pool of invocations for month / 25,000 = 5 function invocations

Or - 5 functions calls per day * 1,000 users * 25 days = 25,000 * 5 = 125,000

So (if I did my math right – and call me out if I did not) then each user can make 5 GraphQL api calls a day. 5.

So, if this is Twitter. Login, View timeline. Paginate 3 times. and… done. You hit your quota.

But, no problem. For $99 month, you get unlimited calls. Problem solved. Or there is a then $7 per 500 call charge. So, make 1,000 more calls, then that’s $14.

Even if you deploy yourself to AWS Serverless (which i have in RW for certain background jobs) you pay AWS per function call.

Things cost money. You just have to spend it well - -and find things that provide the most value.

And for my money, Netlify (and Vercel and others) as well as Auth0 or Magic or Netlify Identity is money well spent - -so I can focus on building my app, building a great UX, focus on my app and not the auth.

Please, I do not mean to discourage – in fact I encourage that a custom auth client be made – I just want to be transparent in the responsibilities and effort and costs to make it so.

Happy to discuss further.

2 Likes