搜索
首页web前端css教程让我们使用nodejs和graphql创建自己的身份验证API

Let's Create Our Own Authentication API with Nodejs and GraphQL

对于刚接触GraphQL的开发者来说,身份验证是极具挑战性的任务之一。其中涉及许多技术考量,包括选择易于设置的ORM、如何生成安全的令牌和哈希密码,甚至包括使用哪个HTTP库以及如何使用它。

本文重点介绍本地身份验证。这可能是现代网站处理身份验证最流行的方式,它通过请求用户的电子邮件密码来实现(与使用Google身份验证相反)。

此外,本文使用Apollo Server 2、JSON Web Tokens (JWT)和Sequelize ORM来构建一个Node.js身份验证API。

身份验证处理

即登录系统:

  • 身份验证识别或验证用户。
  • 授权验证已认证用户可以访问的路由(或应用程序的各个部分)。

实现此流程的步骤如下:

  1. 用户使用密码和电子邮件注册。
  2. 用户的凭据存储在数据库中。
  3. 注册完成后,用户将被重定向到登录页面。
  4. 经过身份验证后,用户将被授予访问特定资源的权限。
  5. 用户的状态存储在任何一种浏览器存储介质(例如localStorage、cookie、会话)或JWT中。

先决条件

在深入实现之前,以下是一些你需要遵循的步骤。

  • Node.js 6或更高版本
  • Yarn(推荐)或NPM
  • GraphQL Playground
  • GraphQL和Node.js的基础知识
  • …一颗求知的心!

依赖项

这是一个很长的列表,让我们开始吧:

  • Apollo Server: 一个开源的GraphQL服务器,兼容任何类型的GraphQL客户端。在这个项目中,我们不会使用Express作为我们的服务器。相反,我们将利用Apollo Server的功能来公开我们的GraphQL API。
  • bcryptjs: 我们希望将用户密码哈希到我们的数据库中。这就是为什么我们将使用bcrypt。它依赖于Web Crypto API的getRandomValues接口来获取安全的随机数。
  • dotenv: 我们将使用dotenv从我们的.env文件中加载环境变量。
  • jsonwebtoken: 用户登录后,每个后续请求都将包含JWT,允许用户访问使用该令牌允许的路由、服务和资源。jsonwebtoken将用于生成JWT,用于验证用户身份。
  • nodemon: 一个工具,通过在检测到目录更改时自动重新启动Node应用程序来帮助开发基于Node的应用程序。我们不希望每次代码发生更改时都关闭和启动服务器。Nodemon每次都会检查我们的应用程序中的更改,并自动重新启动服务器。
  • mysql2: Node.js的SQL客户端。我们需要它来连接到我们的SQL服务器,以便我们可以运行迁移。
  • sequelize: Sequelize是一个基于Promise的Node ORM,用于Postgres、MySQL、MariaDB、SQLite和Microsoft SQL Server。我们将使用Sequelize自动生成我们的迁移和模型。
  • sequelize cli: 我们将使用Sequelize CLI来运行Sequelize命令。使用yarn add --global sequelize-cli在终端全局安装它。

设置目录结构和开发环境

让我们创建一个全新的项目。创建一个新文件夹,并在其中创建以下内容:

<code>yarn init -y</code>

-y标志表示我们对所有yarn init问题都选择yes,并使用默认值。

我们还应该在文件夹中放置一个package.json文件,因此让我们安装项目依赖项:

<code>yarn add apollo-server bcryptjs dotenv jsonwebtoken nodemon sequelize sqlite3</code>

接下来,让我们将Babel添加到我们的开发环境中:

<code>yarn add babel-cli babel-preset-env babel-preset-stage-0 --dev</code>

现在,让我们配置Babel。在终端中运行touch .babelrc。这将创建一个并打开一个Babel配置文件,在其中我们将添加以下内容:

<code>{
  "presets": ["env", "stage-0"]
}</code>

如果我们的服务器启动并迁移数据,那就更好了。我们可以通过使用以下内容更新package.json来自动执行此操作:

<code>"scripts": {
  "migrate": " sequelize db:migrate",
  "dev": "nodemon src/server --exec babel-node -e js",
  "start": "node src/server",
  "test": "echo \"Error: no test specified\" && exit 1"
},</code>

这是我们目前完整的package.json文件:

<code>{
  "name": "graphql-auth",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "migrate": " sequelize db:migrate",
    "dev": "nodemon src/server --exec babel-node -e js",
    "start": "node src/server",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "dependencies": {
    "apollo-server": "^2.17.0",
    "bcryptjs": "^2.4.3",
    "dotenv": "^8.2.0",
    "jsonwebtoken": "^8.5.1",
    "nodemon": "^2.0.4",
    "sequelize": "^6.3.5",
    "sqlite3": "^5.0.0"
  },
  "devDependencies": {
    "babel-cli": "^6.26.0",
    "babel-preset-env": "^1.7.0",
    "babel-preset-stage-0": "^6.24.1"
  }
}</code>

现在我们的开发环境已经设置好了,让我们转向数据库,我们将把东西存储在那里。

数据库设置

我们将使用MySQL作为我们的数据库,并使用Sequelize ORM进行关系映射。运行sequelize init(假设你之前已经全局安装了它)。该命令应该创建三个文件夹:/config /models和/migrations。此时,我们的项目目录结构正在形成。

让我们配置我们的数据库。首先,在项目根目录中创建一个.env文件,并粘贴以下内容:

<code>NODE_ENV=development
DB_HOST=localhost
DB_USERNAME=
DB_PASSWORD=
DB_NAME=</code>

然后转到我们刚刚创建的/config文件夹,并将其中的config.json文件重命名为config.js。然后,将以下代码放入其中:

<code>require('dotenv').config()
const dbDetails = {
  username: process.env.DB_USERNAME,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
  host: process.env.DB_HOST,
  dialect: 'mysql'
}
module.exports = {
  development: dbDetails,
  production: dbDetails
}</code>

在这里,我们正在读取我们在.env文件中设置的数据库详细信息。process.env是由Node注入的全局变量,用于表示系统环境的当前状态。

让我们使用适当的数据更新我们的数据库详细信息。打开SQL数据库并创建一个名为graphql_auth的表。我使用Laragon作为我的本地服务器,并使用phpmyadmin来管理数据库表。

无论你使用什么,我们都需要使用最新信息更新.env文件:

<code>NODE_ENV=development
DB_HOST=localhost
DB_USERNAME=graphql_auth
DB_PASSWORD=
DB_NAME=</code>

让我们配置Sequelize。在项目的根目录中创建一个.sequelizerc文件,并粘贴以下内容:

<code>const path = require('path')

module.exports = {
  config: path.resolve('config', 'config.js')
}</code>

现在让我们将我们的配置集成到模型中。转到/models文件夹中的index.js,并编辑config变量。

<code>const config = require(__dirname   '/../../config/config.js')[env]</code>

最后,让我们编写我们的模型。对于这个项目,我们需要一个User模型。让我们使用Sequelize自动生成模型。以下是我们在终端中需要运行的内容来设置它:

<code>sequelize model:generate --name User --attributes username:string,email:string,password:string</code>

让我们编辑它为我们创建的模型。转到/models文件夹中的user.js,并粘贴以下内容:

<code>'use strict';
module.exports = (sequelize, DataTypes) => {
  const User = sequelize.define('User', {
    username: {
      type: DataTypes.STRING,
    },
    email: {
      type: DataTypes.STRING,  
    },
    password: {
      type: DataTypes.STRING,
    }
  }, {});
  return User;
};</code>

在这里,我们为用户名、电子邮件和密码创建了属性和字段。让我们运行迁移来跟踪我们模式中的更改:

<code>yarn migrate</code>

现在让我们编写模式和解析器。

将模式和解析器与GraphQL服务器集成

在本节中,我们将定义我们的模式,编写解析器函数,并将它们公开到我们的服务器上。

模式

在src文件夹中,创建一个名为/schema的新文件夹,并在其中创建一个名为schema.js的文件。粘贴以下代码:

<code>const { gql } = require('apollo-server')
const typeDefs = gql`
  type User {
    id: Int!
    username: String
    email: String!
  }
  type AuthPayload {
    token: String!
    user: User!
  }
  type Query {
    user(id: Int!): User
    allUsers: [User!]!
    me: User
  }
  type Mutation {
    registerUser(username: String, email: String!, password: String!): AuthPayload!
    login (email: String!, password: String!): AuthPayload!
  }
`
module.exports = typeDefs</code>

在这里,我们从apollo-server导入了graphql-tag。Apollo Server需要用gql包装我们的模式。

解析器

在src文件夹中,创建一个名为/resolvers的新文件夹,并在其中创建一个名为resolver.js的文件。粘贴以下代码:

<code>const bcrypt = require('bcryptjs')
const jsonwebtoken = require('jsonwebtoken')
const models = require('../models')
require('dotenv').config()
const resolvers = {
    Query: {
      async me(_, args, { user }) {
        if(!user) throw new Error('You are not authenticated')
        return await models.User.findByPk(user.id)
      },
      async user(root, { id }, { user }) {
        try {
          if(!user) throw new Error('You are not authenticated!')
          return models.User.findByPk(id)
        } catch (error) {
          throw new Error(error.message)
        }
      },
      async allUsers(root, args, { user }) {
        try {
          if (!user) throw new Error('You are not authenticated!')
          return models.User.findAll()
        } catch (error) {
          throw new Error(error.message)
        }
      }
    },
    Mutation: {
      async registerUser(root, { username, email, password }) {
        try {
          const user = await models.User.create({
            username,
            email,
            password: await bcrypt.hash(password, 10)
          })
          const token = jsonwebtoken.sign(
            { id: user.id, email: user.email},
            process.env.JWT_SECRET,
            { expiresIn: '1y' }
          )
          return {
            token, id: user.id, username: user.username, email: user.email, message: "Authentication succesfull"
          }
        } catch (error) {
          throw new Error(error.message)
        }
      },
      async login(_, { email, password }) {
        try {
          const user = await models.User.findOne({ where: { email }})
          if (!user) {
            throw new Error('No user with that email')
          }
          const isValid = await bcrypt.compare(password, user.password)
          if (!isValid) {
            throw new Error('Incorrect password')
          }
          // return jwt
          const token = jsonwebtoken.sign(
            { id: user.id, email: user.email},
            process.env.JWT_SECRET,
            { expiresIn: '1d'}
          )
          return {
           token, user
          }
      } catch (error) {
        throw new Error(error.message)
      }
    }
  },

}
module.exports = resolvers</code>

有很多代码,让我们看看那里发生了什么。

首先,我们导入了我们的模型、bcrypt和jsonwebtoken,然后初始化了我们的环境变量。

接下来是解析器函数。在查询解析器中,我们有三个函数(me、user和allUsers):

  • me查询获取当前登录用户的详细信息。它接受用户对象作为上下文参数。上下文用于提供对我们数据库的访问,该数据库用于通过查询中提供的ID加载用户数据。
  • user查询根据用户的ID获取用户的详细信息。它接受id作为上下文参数和一个用户对象。
  • alluser查询返回所有用户的详细信息。

如果用户状态已登录,则user将是一个对象;如果用户未登录,则user将为null。我们将在我们的mutation中创建此用户。

在mutation解析器中,我们有两个函数(registerUser和loginUser):

  • registerUser接受用户的用户名、电子邮件和密码,并在我们的数据库中使用这些字段创建一个新行。需要注意的是,我们使用bcryptjs包使用bcrypt.hash(password, 10)对用户的密码进行哈希处理。jsonwebtoken.sign同步地将给定的有效负载签名到JSON Web Token字符串(在本例中为用户ID和电子邮件)。最后,如果成功,registerUser将返回JWT字符串和用户资料;如果出现错误,则返回错误消息。
  • login接受电子邮件和密码,并检查这些详细信息是否与提供的详细信息匹配。首先,我们检查电子邮件值是否已存在于用户数据库中的某个位置。
<code>models.User.findOne({ where: { email }})
if (!user) {
  throw new Error('No user with that email')
}</code>

然后,我们使用bcrypt的bcrypt.compare方法来检查密码是否匹配。

<code>const isValid = await bcrypt.compare(password, user.password)
if (!isValid) {
  throw new Error('Incorrect password')
}</code>

然后,就像我们之前在registerUser中所做的那样,我们使用jsonwebtoken.sign来生成JWT字符串。login mutation返回令牌和用户对象。

现在让我们将JWT_SECRET添加到我们的.env文件中。

<code>JWT_SECRET=一个非常长的秘密</code>

服务器

最后,是服务器!在项目的根文件夹中创建一个server.js,并粘贴以下内容:

<code>const { ApolloServer } = require('apollo-server')
const jwt =  require('jsonwebtoken')
const typeDefs = require('./schema/schema')
const resolvers = require('./resolvers/resolvers')
require('dotenv').config()
const { JWT_SECRET, PORT } = process.env
const getUser = token => {
  try {
    if (token) {
      return jwt.verify(token, JWT_SECRET)
    }
    return null
  } catch (error) {
    return null
  }
}
const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: ({ req }) => {
    const token = req.get('Authorization') || ''
    return { user: getUser(token.replace('Bearer', ''))}
  },
  introspection: true,
  playground: true
})
server.listen({ port: process.env.PORT || 4000 }).then(({ url }) => {
  console.log(`? Server ready at ${url}`);
});</code>

在这里,我们导入了schema、resolvers和jwt,并初始化了我们的环境变量。首先,我们使用verify验证JWT令牌。jwt.verify接受令牌和JWT密钥作为参数。

接下来,我们使用接受typeDefs和resolvers的ApolloServer实例创建我们的服务器。

我们有一个服务器!让我们通过在终端中运行yarn dev来启动它。

测试API

现在让我们使用GraphQL Playground测试GraphQL API。我们应该能够通过ID注册、登录和查看所有用户——包括单个用户。

我们将首先打开GraphQL Playground应用程序,或者只需在浏览器中打开localhost://4000即可访问它。

注册用户的Mutation

<code>mutation {
  registerUser(username: "Wizzy", email: "[email protected]", password: "wizzyekpot" ){
    token
  }
}</code>

我们应该得到类似这样的结果:

<code>{
  "data": {
    "registerUser": {
      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTUsImVtYWlsIjoiZWtwb3RAZ21haWwuY29tIiwiaWF0IjoxNTk5MjQwMzAwLCJleHAiOjE2MzA3OTc5MDB9.gmeynGR9Zwng8cIJR75Qrob9bovnRQT242n6vfBt5PY"
    }
  }
}</code>

登录的Mutation

现在让我们使用我们刚刚创建的用户详细信息登录:

<code>mutation {
  login(email:"[email protected]" password:"wizzyekpot"){
    token
  }
}</code>

我们应该得到类似这样的结果:

<code>{
  "data": {
    "login": {
      "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTUsImVtYWlsIjoiZWtwb3RAZ21haWwuY29tIiwiaWF0IjoxNTk5MjQwMzcwLCJleHAiOjE1OTkzMjY3NzB9.PDiBKyq58nWxlgTOQYzbtKJ-HkzxemVppLA5nBdm4nc"
    }
  }
}</code>

太棒了!

单个用户的Query

为了查询单个用户,我们需要将用户令牌作为授权标头传递。转到HTTP标头选项卡。

…并将此内容粘贴进去:

<code>{
  "Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTUsImVtYWlsIjoiZWtwb3RAZ21haWwuY29tIiwiaWF0IjoxNTk5MjQwMzcwLCJleHAiOjE1OTkzMjY3NzB9.PDiBKyq58nWxlgTOQYzbtKJ-HkzxemVppLA5nBdm4nc"
}</code>

这是查询:

<code>query myself{
  me {
    id
    email
    username
  }
}</code>

我们应该得到类似这样的结果:

<code>{
  "data": {
    "me": {
      "id": 15,
      "email": "[email protected]",
      "username": "Wizzy"
    }
  }
}</code>

太棒了! 现在让我们按ID获取用户:

<code>query singleUser{
  user(id:15){
    id
    email
    username
  }
}</code>

这是获取所有用户的查询:

<code>{
  allUsers{
    id
    username
    email
  }
}</code>

总结

在构建需要身份验证的网站时,身份验证是最困难的任务之一。GraphQL使我们能够仅使用一个端点构建完整的身份验证API。Sequelize ORM使创建与SQL数据库的关系变得如此简单,我们几乎不必担心我们的模型。同样值得注意的是,我们不需要HTTP服务器库(如Express),而是使用Apollo GraphQL作为中间件。Apollo Server 2现在使我们能够创建我们自己的库无关的GraphQL服务器!

请查看GitHub上的本教程的源代码。

以上是让我们使用nodejs和graphql创建自己的身份验证API的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
模拟鼠标运动模拟鼠标运动Apr 22, 2025 am 11:45 AM

如果您曾经在现场演讲或课程中必须显示一个互动动画,那么您可能知道它并不总是那么容易与您的幻灯片进行互动

通过Astro Action和Fuse.js为搜索提供动力通过Astro Action和Fuse.js为搜索提供动力Apr 22, 2025 am 11:41 AM

对于Astro,我们可以在构建过程中生成大部分网站,但是有一小部分服务器端代码可以使用Fuse.js之类的搜索功能来处理搜索功能。在此演示中,我们将使用保险丝搜索一组个人“书签”

未定义:第三个布尔值未定义:第三个布尔值Apr 22, 2025 am 11:38 AM

我想在我的一个项目中实现一条通知消息,类似于您在保存文档时在Google文档中看到的信息。换句话说,一个

捍卫三元声明捍卫三元声明Apr 22, 2025 am 11:25 AM

几个月前,我正在使用黑客新闻(就像一个人一样),并且遇到了一篇(现已删除的)文章,内容涉及不使用if语句。如果您是这个想法的新手(就像我

使用网络语音API进行多语言翻译使用网络语音API进行多语言翻译Apr 22, 2025 am 11:23 AM

自科幻小说以来,我们就幻想着与我们交谈的机器。今天这很普遍。即便如此,制造的技术

JetPack Gutenberg块JetPack Gutenberg块Apr 22, 2025 am 11:20 AM

我记得当古腾堡被释放到核心时,因为那天我在WordCamp我们。现在已经过去了几个月,所以我想我们越来越多的人

在VUE中创建可重复使用的分页组件在VUE中创建可重复使用的分页组件Apr 22, 2025 am 11:17 AM

大多数Web应用程序背后的想法是从数据库中获取数据,并以最佳方式将其呈现给用户。当我们处理数据时

使用'盒子阴影”和剪辑路径一起使用'盒子阴影”和剪辑路径一起Apr 22, 2025 am 11:13 AM

让我们对您可以做一些有意义的事情做一些逐步的情况,但是您仍然可以用CSS欺骗来完成它。在这个

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )专业的PHP集成开发工具

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

功能强大的PHP集成开发环境

WebStorm Mac版

WebStorm Mac版

好用的JavaScript开发工具

安全考试浏览器

安全考试浏览器

Safe Exam Browser是一个安全的浏览器环境,用于安全地进行在线考试。该软件将任何计算机变成一个安全的工作站。它控制对任何实用工具的访问,并防止学生使用未经授权的资源。

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器