Last year I wrote an article about JWT.

The article points out that JWT can be used if there is no special requirement for user logout and single-user multi-device login, and the biggest feature of JWT is stateless and non-encryption.

In addition to the user login aspect, you can also use JWT to verify your email, and in fact, your phone, but I have no money, so I can only verify my email.

In addition, I have been practicing in my test field, but so far the front-end code is relatively simple, and there is not even a failure feedback. As for why front-end write simple, it is entirely because the amount of front-end code compared to the back-end is too huge…

Also, if you’re familiar with GraphQL, you can check out the graphQL-Playground for this project.

Shanyue. tech/ POST/jwT-an…

Send verification code

Before verification, a random number is required for email and SMS sending. Use the following code snippet to generate a random six-digit code, which you can also wrap as a function

const verifyCode = Array.from(Array(6), () = >parseInt((Math.random() * 10))).join(' ')
Copy the code

With a traditional stateful solution, a key-value pair of the user’s mailbox and random code is maintained on the server side, while with JWT, a token is returned to the front end, which is then used to verify the verification code.

We know that JWT only verifies the integrity of data, not encrypts it. In this case, if the user mailbox and the verification code are matched, but the JWT transmits data in plaintext, the verification code will be leaked

// Put it in plain text, and the parity code is leaked
jwt.sign({ email, verifyCode }, config.jwtSecret, { expiresIn: '30m' })
Copy the code

So how do you make sure that the check code is not compromised and that the data is checked correctly

We know that secret will not be leaked, so put the verification code in secret to complete the pairing

// Allow another half an hour to expire
const token = jwt.sign({ email }, config.jwtSecret + verifyCode, { expiresIn: '30m' })
Copy the code

When the server sends the email, the token is passed to the front end and then sent to the back end for verification at the time of registration. This is the code of the graphQL verification in my project. If you don’t know graphQL, you can use it as pseudocode, and you should be able to read it

type Mutation {
  # Send email
  # return a token that is used to verify the verification code
  sendEmailVerifyCode (
    email: String! @constraint(format: "email")
  ): String!
}
Copy the code
const Mutation = {
  async sendEmailVerifyCode (root, { email }, { email: emailService }) {
    // Generate six random numbers
    const verifyCode = Array.from(Array(6), () = >parseInt((Math.random() * 10))).join(' ')
    // TODO can be placed in a message queue, but not much, and the Mutation is limited to streams.
    // The same as dotting, do not care about the result
    emailService.send({
      to: email, 
      subject: Account Security -- Email Verification.html: <span style="color:#337ab7">${verifyCode}</span> (In order to ensure the security of your account, please verify within 30 minutes) \n\n poetry string song team '
    })
    return jwt.sign({ email }, config.jwtSecret + verifyCode, { expiresIn: '30m'}}})Copy the code

As an aside, there are a few things to think about when sending an email, but I’ll leave that aside and write an article to summarize it once it’s implemented

  1. If mail is provided by a service, how do you consider asynchronous and synchronous services
  2. Message queue processing, sending mail does not require reliability, more like UDP
  3. How to RateLimit traffic in order to avoid users sending a large number of emails in a short time

As an aside, an image verification code is required to verify user authenticity and limit traffic before sending emails or SMS messages. The image verification code can also be implemented through JWT

registered

It is easy to register the data from the client. If the data is successfully verified, it can be directly entered into the library

type Mutation {
  # registered
  createUser (
    name: String!
    password: String!
    email: String! @constraint(format: "email")
    verifyCode: String!
    Token to send email to client
    token: String!). : User!
}
Copy the code
const Mutation = {
  async createUser (root, { name, password, email, verifyCode, token }, { models }) {
    const { email: verifyEmail } = jwt.verify(token, config.jwtSecret + verifyCode)
    if(email ! == verifyEmail) {throw new Error('Please enter the correct email address')}const user = await models.users.create({
      name,
      email,
      // Add salt to the password
      password: hash(password)
    })
    return user
  }
}
Copy the code

There is a detail here, the password is irreversibly processed using MD5 and a single parameter salt

function hash (str) {
  return crypto.createHash('md5').update(`${str}-${config.salt}`.'utf8').digest('hex')}Copy the code

As an aside, can salt be set to the same string as JWT secret?

As an aside, the Error entered in the correct mailbox should not be sent to Sentry (alarm system) obviously, and some Error information can be directly displayed in the front end, how to standardize and classify the Error

The checksum code is compared with JWT by traditional method

If the traditional method is used, only a key/value database is needed to maintain the corresponding relationship between mobile phone number/email and verification code, which is much simpler than JWT.

The login

A graphQL code that implements login using JWT and places user_id and user_role in payload

type Mutation {
  If null is returned, the login fails
  createUserToken (
    email: String! @constraint(format: "email")
    password: String!). : String
}
Copy the code
const Mutation = {
  async createUserToken (root, { email, password }, { models }) {
    const user = await models.users.findOne({
      where: {
        email,
        password: hash(password)
      },
      attributes: ['id'.'role'],
      raw: true
    })
    if(! User) {// return null to indicate user login failedreturn
    }
    return jwt.sign(user, config.jwtSecret, { expiresIn: '1d'}}})Copy the code

Pay attention to the public number shanyuexixing, record my technical growth, welcome to exchange