Let's Create Our Own Authentication API with Nodejs and GraphQL
Authentication is one of the most challenging tasks for developers new to GraphQL. There are many technical considerations involved, including choosing an ORM that is easy to set, how to generate a secure token and hash password, and even which HTTP library to use and how to use it.
This article focuses on local authentication . This is probably the most popular way for modern websites to handle authentication, which is achieved by requesting users’ emails and passwords (as opposed to using Google Authentication).
In addition, this article uses Apollo Server 2, JSON Web Tokens (JWT) and Sequelize ORM to build a Node.js authentication API.
Authentication processing
Log in to the system:
- Authentication identifies or authenticates users.
- Authorizes verification of routes (or parts of the application) that an authenticated user can access.
The steps to implement this process are as follows:
- Users register with password and email.
- The user's credentials are stored in the database.
- After registration is completed, the user will be redirected to the login page.
- After authentication, the user will be granted access to a specific resource.
- The user's status is stored in any browser storage medium (eg, localStorage, cookies, sessions) or in a JWT.
Prerequisites
Before you go deeper, here are some steps you need to follow.
- Node.js 6 or higher
- Yarn (recommended) or NPM
- GraphQL Playground
- Basics of GraphQL and Node.js
- …A heart that seeks knowledge!
Dependencies
Here is a long list, let's get started:
- Apollo Server : An open source GraphQL server compatible with any type of GraphQL client. In this project, we will not use Express as our server. Instead, we will take advantage of the capabilities of Apollo Server to expose our GraphQL API.
- bcryptjs : We want to hash the user password into our database. That's why we're going to use bcrypt. It relies on the getRandomValues interface of the Web Crypto API to get secure random numbers.
- dotenv : We will use dotenv to load environment variables from our .env file.
- jsonwebtoken : After the user logs in, each subsequent request will contain a JWT, allowing the user to access the routes, services, and resources allowed using the token. jsonwebtoken will be used to generate JWTs, which are used to verify the user's identity.
- nodemon : A tool that helps develop Node-based applications by automatically restarting Node applications when a directory change is detected. We don't want the server to be shut down and started every time the code changes. Nodemon checks for changes in our application every time and automatically restarts the server.
- mysql2 : Node.js' SQL client. We need it to connect to our SQL server so that we can run the migration.
- sequelize : Sequelize is a Promise-based Node ORM used in Postgres, MySQL, MariaDB, SQLite and Microsoft SQL Server. We will use Sequelize to automatically generate our migrations and models.
- sequelize cli : We will use the Sequelize CLI to run the Sequelize command. Install it globally in the terminal using yarn add --global sequelize-cli.
Setting up directory structure and development environment
Let's create a brand new project. Create a new folder and create the following in it:
<code>yarn init -y</code>
The -y flag means we select yes for all yarn init issues and use the default value.
We should also place a package.json file in the folder, so let's install the project dependencies:
<code>yarn add apollo-server bcryptjs dotenv jsonwebtoken nodemon sequelize sqlite3</code>
Next, let's add Babel to our development environment:
<code>yarn add babel-cli babel-preset-env babel-preset-stage-0 --dev</code>
Now, let's configure Babel. Run touch .babelrc in the terminal. This will create and open a Babel configuration file where we will add the following:
<code>{ "presets": ["env", "stage-0"] }</code>
It would be even better if our servers started and migrated data. We can do this automatically by updating package.json with the following:
<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>
Here is our current complete package.json file:
<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>
Now that our development environment is set up, let's move to the database, we'll store things there.
Database settings
We will use MySQL as our database and use Sequelize ORM for relational mapping. Run sequelize init (assuming you have installed it globally before). This command should create three folders: /config /models and /migrations. At this time, our project directory structure is forming.
Let's configure our database. First, create a .env file in the project root directory and paste the following:
<code>NODE_ENV=development DB_HOST=localhost DB_USERNAME= DB_PASSWORD= DB_NAME=</code>
Then go to the /config folder we just created and rename the config.json file in it to config.js. Then, put the following code into it:
<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>
Here we are reading the database details we set in the .env file. process.env is a global variable injected by Node to represent the current state of the system environment.
Let's update our database details with the appropriate data. Open the SQL database and create a table named graphql_auth. I use Laragon as my local server and use phpmyadmin to manage the database tables.
No matter what you use, we need to update the .env file with the latest information:
<code>NODE_ENV=development DB_HOST=localhost DB_USERNAME=graphql_auth DB_PASSWORD= DB_NAME=</code>
Let's configure Sequelize. Create a .sequelizerc file in the root directory of the project and paste the following:
<code>const path = require('path') module.exports = { config: path.resolve('config', 'config.js') }</code>
Now let's integrate our configuration into the model. Go to index.js in /models folder and edit the config variable.
<code>const config = require(__dirname '/../../config/config.js')[env]</code>
Finally, let's write our model. For this project, we need a User model. Let's use Sequelize to automatically generate the model. Here is what we need to run in the terminal to set it up:
<code>sequelize model:generate --name User --attributes username:string,email:string,password:string</code>
Let's edit the model it creates for us. Go to user.js in /models folder and paste the following:
<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>
Here we create properties and fields for usernames, emails, and passwords. Let's run the migration to track changes in our schema:
<code>yarn migrate</code>
Now let's write the pattern and parser.
Integrate schema and parser with GraphQL server
In this section, we will define our patterns, write parser functions, and expose them to our server.
model
In the src folder, create a new folder named /schema and create a file named schema.js in it. Paste the following code:
<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>
Here, we import graphql-tag from apollo-server. Apollo Server needs to wrap our pattern with gql.
Parser
In the src folder, create a new folder named /resolvers and create a file named resolver.js in it. Paste the following code:
<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>
There is a lot of code, let's see what's going on there.
First, we import our model, bcrypt, and jsonwebtoken, and then initialize our environment variables.
Next is the parser function. In the query parser, we have three functions (me, user, and allUsers):
- Me query to get detailed information of the currently logged in user. It accepts user objects as context parameters. The context is used to provide access to our database, which is used to load user data through the ID provided in the query.
- The user query obtains the user's detailed information based on the user's ID. It accepts id as context parameter and a user object.
- The alluser query returns the details of all users.
If the user status is logged in, user will be an object; if the user is not logged in, user will be null. We will create this user in our mutation.
In the mutation parser, we have two functions (registerUser and loginUser):
- registerUser accepts the user's username, email, and password and uses these fields to create a new row in our database. It should be noted that we use the bcryptjs package to hash the user's password using bcrypt.hash(password, 10). jsonwebtoken.sign synchronously signs the given payload to a JSON Web Token string (in this case user ID and email). Finally, registerUser will return the JWT string and user profile if successful; if an error occurs, an error message will be returned.
- login accepts email and password and checks if these details match the provided details. First, we check if the email value already exists somewhere in the user database.
<code>models.User.findOne({ where: { email }}) if (!user) { throw new Error('No user with that email') }</code>
Then, we use bcrypt's bcrypt.compare method to check if the password matches.
<code>const isValid = await bcrypt.compare(password, user.password) if (!isValid) { throw new Error('Incorrect password') }</code>
Then, like we did in registerUser before, we use jsonwebtoken.sign to generate the JWT string. login mutation returns token and user objects.
Now let's add JWT_SECRET to our .env file.
<code>JWT_SECRET=一个非常长的秘密</code>
server
Finally, it's the server! Create a server.js in the root folder of the project and paste the following:
<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>
Here we import schema, resolvers, and jwt and initialize our environment variables. First, we use verify to verify the JWT token. jwt.verify accepts token and JWT key as parameters.
Next, we create our server using an ApolloServer instance that accepts typeDefs and resolvers.
We have a server! Let's start it by running yarn dev in the terminal.
Test API
Now let's test the GraphQL API using GraphQL Playground. We should be able to register, log in and view all users – including individual users – through ID.
We will first open the GraphQL Playground app, or simply open localhost://4000 in the browser to access it.
Mutation for registered users
<code>mutation { registerUser(username: "Wizzy", email: "[email protected]", password: "wizzyekpot" ){ token } }</code>
We should get results like this:
<code>{ "data": { "registerUser": { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTUsImVtYWlsIjoiZWtwb3RAZ21haWwuY29tIiwiaWF0IjoxNTk5MjQwMzAwLCJleHAiOjE2MzA3OTc5MDB9.gmeynGR9Zwng8cIJR75Qrob9bovnRQT242n6vfBt5PY" } } }</code>
Login Mutation
Now let's log in with the user details we just created:
<code>mutation { login(email:"[email protected]" password:"wizzyekpot"){ token } }</code>
We should get results like this:
<code>{ "data": { "login": { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTUsImVtYWlsIjoiZWtwb3RAZ21haWwuY29tIiwiaWF0IjoxNTk5MjQwMzcwLCJleHAiOjE1OTkzMjY3NzB9.PDiBKyq58nWxlgTOQYzbtKJ-HkzxemVppLA5nBdm4nc" } } }</code>
marvelous!
Query for individual users
In order to query a single user, we need to pass the user token as an authorization header. Go to the HTTP Header tab.
...and paste this content in:
<code>{ "Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MTUsImVtYWlsIjoiZWtwb3RAZ21haWwuY29tIiwiaWF0IjoxNTk5MjQwMzcwLCJleHAiOjE1OTkzMjY3NzB9.PDiBKyq58nWxlgTOQYzbtKJ-HkzxemVppLA5nBdm4nc" }</code>
Here is the query:
<code>query myself{ me { id email username } }</code>
We should get results like this:
<code>{ "data": { "me": { "id": 15, "email": "[email protected]", "username": "Wizzy" } } }</code>
Great! Now let's get the user by ID:
<code>query singleUser{ user(id:15){ id email username } }</code>
Here is a query to get all users:
<code>{ allUsers{ id username email } }</code>
Summarize
Authentication is one of the most difficult tasks when building a website that requires authentication. GraphQL enables us to build a complete authentication API using only one endpoint. Sequelize ORM makes it so easy to create relationships with SQL databases that we hardly have to worry about our model. It is also worth noting that we do not need HTTP server libraries (such as Express) but use Apollo GraphQL as middleware. Apollo Server 2 now enables us to create our own library-independent GraphQL servers!
Please check the source code for this tutorial on GitHub.
The above is the detailed content of Let's Create Our Own Authentication API with Nodejs and GraphQL. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

AI Hentai Generator
Generate AI Hentai for free.

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics



If you’ve recently started working with GraphQL, or reviewed its pros and cons, you’ve no doubt heard things like “GraphQL doesn’t support caching” or

With the recent climb of Bitcoin’s price over 20k $USD, and to it recently breaking 30k, I thought it’s worth taking a deep dive back into creating Ethereum

No matter what stage you’re at as a developer, the tasks we complete—whether big or small—make a huge impact in our personal and professional growth.

It's out! Congrats to the Vue team for getting it done, I know it was a massive effort and a long time coming. All new docs, as well.

I'd say "website" fits better than "mobile app" but I like this framing from Max Lynch:

I had someone write in with this very legit question. Lea just blogged about how you can get valid CSS properties themselves from the browser. That's like this.

The other day, I spotted this particularly lovely bit from Corey Ginnivan’s website where a collection of cards stack on top of one another as you scroll.

I was just chatting with Eric Meyer the other day and I remembered an Eric Meyer story from my formative years. I wrote a blog post about CSS specificity, and
