让我们使用nodejs和graphql创建自己的身份验证API
对于刚接触GraphQL的开发者来说,身份验证是极具挑战性的任务之一。其中涉及许多技术考量,包括选择易于设置的ORM、如何生成安全的令牌和哈希密码,甚至包括使用哪个HTTP库以及如何使用它。
本文重点介绍本地身份验证。这可能是现代网站处理身份验证最流行的方式,它通过请求用户的电子邮件和密码来实现(与使用Google身份验证相反)。
此外,本文使用Apollo Server 2、JSON Web Tokens (JWT)和Sequelize ORM来构建一个Node.js身份验证API。
身份验证处理
即登录系统:
- 身份验证识别或验证用户。
- 授权验证已认证用户可以访问的路由(或应用程序的各个部分)。
实现此流程的步骤如下:
- 用户使用密码和电子邮件注册。
- 用户的凭据存储在数据库中。
- 注册完成后,用户将被重定向到登录页面。
- 经过身份验证后,用户将被授予访问特定资源的权限。
- 用户的状态存储在任何一种浏览器存储介质(例如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中文网其他相关文章!

热AI工具

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

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

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

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

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)