Simple email/password authentication

Hello everybody, first post here!

I’m exploring Redwood and I find it fantastic, great work!

I need some help in trying to figure out how to make a simple email/password authentication. I already searched everywhere but I can’t find anything that works or explains how the auth work.
I have also explored Redwood sources.

I’m using version 0.14.

I was thinking about doing it in this way:

  • create a custom sdl file with a login mutation
  • create a custom service with a login function
  • create a custom auth client that will call the login mutation
  • AuthProvider with type “custom”, passing a custom auth client

but…that won’t work because AuthProvider is the parent of RedwoodProvider, that instantiate the ApolloClient, so we can’t use GraphQL in the auth client.

Also how can we pass the email/password from the form to the auth client? Is it even possible?

Anyone has already done that? Is it possible or I’m missing something or over complicating things?

Thanks!

1 Like

Welcome @luke!

Why not bypass graphql entirely? You could build your own function in api/function that would call the proper service with your connection logic. It’s a wild guess though :slight_smile: but it would allow you to avoid the Apollo instantiation trap.

This being said it would also mean that the sdl would become useless. And maybe you are missing any code related to schema? Where will you store your users and credentials?

Ah! you’re right, I wasn’t thinking about making a custom function, I should give it a try.

I’m just starting to plan the thing, not coded anything yet. I was exploring Redwood sources to learn how auth works internally.

My idea is to have a User model and save the email and encrypted password in the database. On login request check the email and password, sign a jwt if the credentials are valid and return it to the client.

That should do it, unless I’m missing something about how Redwood auth works.

It should be enough to begin with :slight_smile:

There’s been some discussion about this somehow here: Auth: what about signing up?
You are on good tracks, I’m only referring to this so you can see a bit of what the community discussed on the matter - there are other threads with relevant information as well.

Also, adding a new provider is a very good exercise to figure out how the auth works, figured it first hand :). If you want to give a try I’m willing to assist.

If you only need the logged in user details to work on the web site you could do a down and dirty version:

  • Create a login SDL (or reuse one the User one, if you have it)
  • Create a login service (or reuse the User one)
  • Create something like a findUserByEmailAndPassword that either raises an error if the user isn’t found (and that you catch on the frontend) or return the found User
  • If the user is found, save those user details in localStorage

Now you just check localStorage for your user data—if it’s there then they’re logged in. You could have a logout function on the web side that just clears out localStorage.

Alternatively to the SDL + service implementation you could just create a custom function as @noire.munich mentions and make it something simple like a RESTful call.

If you did want the logged in user on the API side you’d have to invent your own way to get that info up there. Maybe every GraphQL call includes a variable for the user’s ID? This wouldn’t be the most secure thing in the world (it would help if the ID is a UUID or CUID) but it would work.

One of the benefits of using the Redwood Auth is that you get the same user data on both the web and api sides for free! Well, for free after someone invents a way to do custom email/password login and merges it into Redwood. :sweat_smile:

Hi Luke,

Welcome to RW!

First off, this is entirely possible with redwood, because we implemented custom JWT auth for our website, just without the username/password bit, instead using github for that portion of it. You sound like you know what you’re doing, so it should be no problem for you :), just need a little patience to understand the auth process.

Before we continue… I’ll forward on the same advice I got initially, maybe consider Auth0 unless you have a really good reason not to, because it’ll definitely be less maintenance. But incase you still want to, here’s a summary:

1. Create a GraphQL handler for your login form, according to Rob’s post above
This will let you post a username and password, check with your user table on the DB, and return a JWT token in the response. At this point you can save it in localStorage, or however you’d like to.

2. Start with the standard syntax for enabling auth in your app
https://redwoodjs.com/docs/authentication. For now pick one of the options, just to roll with it, before you write your custom handlers.

There’s a few little insights here that’s really important, in the JSX code there’s:

a) AuthProvider - this is the component that will let the FE know whether you’re logged in or not. You’ll want to write a custom auth provider later, but for now let’s keep investigating.

b) In the JSX, notice how it says type="netlify" or type="auth0". This is important because every graphql request it sends will have two headers for auth: auth-provider which is this type, and Authorization which is going to be your token. You need to set the type to “custom” or whatever you want so that in the api you can handle parsing/verifying the token yourself instead of the built in providers

c) Notice the client={auth0} prop? This is your authClient, whose job is to expose functions for logging in, logout, and getting your token from localStorage (among other things). You’ll need to write a custom auth client here.

There’s an old post I originally wrote here Custom github JWT Auth with Redwood Auth. Part 2 in the first post is where you can see simple implementations of both the AuthProvider and a custom authClient

3. Wrtie your custom validator on the api side
You write this in src/lib/auth.js in the getCurrentUser function. The purpose of this is to validate the JWT that the graphql handler receives, and to return the user’s details so it can be used in services later.

Example in post 11 of the thread above :slight_smile:

I hope this helps you get started!

I tried coding this and it works quite good!

Here’s what I have done:

  • use “custom” in type prop of AuthProvider
  • create a custom LocalAuthClient, passing it to the client prop of AuthProvider. I tested both a custom GraphQL mutation and a custom function, both works, but the request must be done manually using fetch, ApolloClient can’t be used as stated above. The client also handles the jwt in the local storage, adding it on login, removing it on logout and decoding the data in getUserMetadata
  • In the api logic I search for a user in the db using the email and then compare the password to check if everything is good. If a user is found, I sign a jwt with the user data and return it
  • the getCurrentUser method in auth.js verifies the jwt and returns the data
  • private / public keys are needed of course to sign and verify the jwt, I stored them in the project root

I need to test this more, but looks good and I can now use useAuth in web!

Thanks everyone!

A question for the core team: do you want to add support for local auth in Redwood or do you think that the custom type is enough?

2 Likes

I keep forgetting about public/private keys when signing the JWT – and fallback on just a private secret (but of course can’t use that in many cases when the secret can’t really be kept … secret like in a web client request).

Going to try to use those more when securing functions/webhooks from an outside source.

Thanks for reminding me about this.

Hey y’all, I did a bunch of research on passport hashing and verification for Blitz, and I settled on using secure-password as the best library to use.

Here’s the code we have inside the app that provides nice hashPassword, verifyPassword, and authenticateUser helper functions.

Maybe you’ll find it useful

import SecurePassword from "secure-password"
import db, {User} from "db"  // import prisma database

const SP = new SecurePassword()

export const hashPassword = async (password: string) => {
  const hashedBuffer = await SP.hash(Buffer.from(password))
  return hashedBuffer.toString("base64")
}
export const verifyPassword = async (hashedPassword: string, password: string) => {
  return await SP.verify(Buffer.from(password), Buffer.from(hashedPassword, "base64"))
}

export const authenticateUser = async (email: string, password: string) => {
  const user = await db.user.findOne({where: {email}})

  if (!user || !user.hashedPassword) throw new AuthenticationError()

  switch (await verifyPassword(user.hashedPassword, password)) {
    case SecurePassword.VALID:
      break
    case SecurePassword.VALID_NEEDS_REHASH:
      // Upgrade hashed password with a more secure hash
      const improvedHash = await hashPassword(password)
      await db.user.update({where: {id: user.id}, data: {hashedPassword: improvedHash}})
      break
    default:
      throw new AuthenticationError()
  }

  delete user.hashedPassword
  return user as Omit<User, "hashedPassword">
}

That code requires email and hashedPassword fields on your User model

model User {
  id        Int      @default(autoincrement()) @id
  email     String   @unique
  hashedPassword String?
}
8 Likes

Hi @flybayer! :rocket: I can’t adequately put into words how awesome it is to see your picture and this wonderful response + substantial help here. It just represents everything about what open source community can be, and is, when it’s at its best.

I look forward to paying it forward. (Take me, and us, up on it, ok?)


For those who don’t know, Brandon started https://blitzjs.com/

3 Likes

Woah great, thank you for that!

I’m a bit busy at work these days, but in the coming week I will finish the email/pass auth and reply here with a detailed post on how to implement it, hoping that it will be useful to the community!

As promised here’s a repository with a complete example :slightly_smiling_face:

Thanks everyone!

1 Like

This looks like a great implementation to me @luke

Only thing I would add is, you should add the file for src/lib/auth (use redwood generator) yarn rw g auth custom I think.

This way you can demonstrate how you’re decoding the token in getCurrentUser, and verifying if auth is required in services in requireAuth and the example will be complete!

Also love that @flybayer is in this thread and helping out. Open source ftw!

Nice!

I saw that the public/private keys used to sign the JWT are read from the filesystem:

Curious – if deploying to Netlify/Vercel, is this still possible? – or would they have to be stored in env?

you should add the file for src/lib/auth

It’s already there, maybe you were looking in the web package? It’s a fully functional example :grin:

deploying to Netlify/Vercel, is this still possible?

I have tried this only locally and on a vps, so I can’t say if it works on Netlify / Vercel.

Found this maybe it’s the way.

Edit:
Or we could read an env directly, but there is a size limit.

Oh, that can be super useful. I have got to try Vercel one of these days. Thanks for making me aware of this!

Just another thing that I think is missing.

There’s a way to rate limit the api? It would be a good default to have it enabled on auth apis

You’re right, my bad!

For JWTs, what we’re doing is replacing it inline with a build script we run. The reason for this is because Lambdas have a 4096 byte limit on env variables, and the JWT RSA keys tend to exceed this.

Most of the rate-limiters, throttles that I’ve seen are backed by some storage (like Redis) to track/increment the requests per time/ip address/user.

Wonder if there is a rate limit SaaS (app idea?)