├── .gitignore ├── .sequelizerc ├── .env ├── .env.example ├── config └── config.js ├── models ├── user.js └── index.js ├── data ├── schema.js └── resolvers.js ├── package.json ├── server.js ├── migrations └── 20180129094445-create-user.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.sequelizerc: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | 'config': path.resolve('config', 'config.js') 5 | } -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | DB_HOST=localhost 3 | DB_USERNAME=root 4 | DB_PASSWORD= 5 | DB_NAME=graphql_jwt_auth 6 | JWT_SECRET=somereallylongsecretkey -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | DB_HOST=localhost 3 | DB_USERNAME=root 4 | DB_PASSWORD= 5 | DB_NAME=graphql_jwt_auth 6 | JWT_SECRET=somereallylongsecretkey -------------------------------------------------------------------------------- /config/config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('dotenv').config() 4 | 5 | const dbDetails = { 6 | username: process.env.DB_USERNAME, 7 | password: process.env.DB_PASSWORD, 8 | database: process.env.DB_NAME, 9 | host: process.env.DB_HOST, 10 | dialect: 'mysql' 11 | } 12 | 13 | module.exports = { 14 | development: dbDetails, 15 | production: dbDetails 16 | } 17 | -------------------------------------------------------------------------------- /models/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | var User = sequelize.define('User', { 4 | username: DataTypes.STRING, 5 | email: DataTypes.STRING, 6 | password: DataTypes.STRING 7 | }, { 8 | classMethods: { 9 | associate: function(models) { 10 | // associations can be defined here 11 | } 12 | } 13 | }); 14 | return User; 15 | }; -------------------------------------------------------------------------------- /data/schema.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { makeExecutableSchema } = require('graphql-tools') 4 | const resolvers = require('./resolvers') 5 | 6 | // Define our schema using the GraphQL schema language 7 | const typeDefs = ` 8 | type User { 9 | id: Int! 10 | username: String! 11 | email: String! 12 | } 13 | 14 | type Query { 15 | me: User 16 | } 17 | 18 | type Mutation { 19 | signup (username: String!, email: String!, password: String!): User 20 | login (email: String!, password: String!): String 21 | } 22 | ` 23 | 24 | module.exports = makeExecutableSchema({ typeDefs, resolvers }) 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-jwt-auth", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node server.js" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "apollo-server-express": "^1.3.2", 14 | "bcrypt": "^1.0.3", 15 | "body-parser": "^1.18.2", 16 | "dotenv": "^4.0.0", 17 | "express": "^4.16.2", 18 | "express-jwt": "^5.3.0", 19 | "graphql": "^0.12.3", 20 | "graphql-tools": "^2.19.0", 21 | "jsonwebtoken": "^8.1.1", 22 | "mysql2": "^1.5.1", 23 | "sequelize": "^4.32.2" 24 | } 25 | } -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const express = require('express') 4 | const bodyParser = require('body-parser') 5 | const { graphqlExpress } = require('apollo-server-express') 6 | const schema = require('./data/schema') 7 | const jwt = require('express-jwt') 8 | require('dotenv').config() 9 | 10 | const PORT = 3000 11 | 12 | // create our express app 13 | const app = express() 14 | 15 | // auth middleware 16 | const auth = jwt({ 17 | secret: process.env.JWT_SECRET, 18 | credentialsRequired: false 19 | }) 20 | 21 | // graphql endpoint 22 | app.use( 23 | '/api', 24 | bodyParser.json(), 25 | auth, 26 | graphqlExpress(req => ({ 27 | schema, 28 | context: { 29 | user: req.user 30 | } 31 | })) 32 | ) 33 | 34 | app.listen(PORT, () => { 35 | console.log(`The server is running on http://localhost:${PORT}/api`) 36 | }) 37 | -------------------------------------------------------------------------------- /migrations/20180129094445-create-user.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('Users', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | username: { 12 | type: Sequelize.STRING 13 | }, 14 | email: { 15 | type: Sequelize.STRING 16 | }, 17 | password: { 18 | type: Sequelize.STRING 19 | }, 20 | createdAt: { 21 | type: Sequelize.DATE, 22 | allowNull: false 23 | }, 24 | 25 | updatedAt: { 26 | type: Sequelize.DATE, 27 | allowNull: false 28 | } 29 | }) 30 | }, 31 | down: (queryInterface, Sequelize) => { 32 | return queryInterface.dropTable('Users') 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # graphql-jwt-auth 2 | 3 | Adding authentication with JWT to a GraphQL server 4 | 5 | ## Getting Started 6 | 7 | Clone the project repository by running the command below if you use SSH 8 | 9 | ```bash 10 | git clone git@github.com:ammezie/graphql-jwt-auth.git 11 | ``` 12 | 13 | If you use https, use this instead 14 | 15 | ```bash 16 | git clone https://github.com/ammezie/graphql-jwt-auth.git 17 | ``` 18 | 19 | After cloning, run: 20 | 21 | ```bash 22 | npm install 23 | ``` 24 | 25 | Rename `.env.example` to `.env` then fill in your database detail and your JWT secret: 26 | 27 | ```txt 28 | NODE_ENV=development 29 | DB_HOST=localhost 30 | DB_USERNAME=root 31 | DB_PASSWORD= 32 | DB_NAME=graphql_jwt_auth 33 | JWT_SECRET= 34 | ``` 35 | 36 | Then run the migration: 37 | 38 | ```bash 39 | sequelize db:migrate 40 | ``` 41 | 42 | And finally, start the application: 43 | 44 | ```bash 45 | npm start 46 | ``` 47 | 48 | The server will be running on [http://localhost:3000/api](http://localhost:3000/api). 49 | -------------------------------------------------------------------------------- /models/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var fs = require('fs') 4 | var path = require('path') 5 | var Sequelize = require('sequelize') 6 | var basename = path.basename(__filename) 7 | var env = process.env.NODE_ENV || 'development' 8 | var config = require(__dirname + '/../config/config.js')[env] 9 | var db = {} 10 | 11 | if (config.use_env_variable) { 12 | var sequelize = new Sequelize(process.env[config.use_env_variable]) 13 | } else { 14 | var sequelize = new Sequelize( 15 | config.database, 16 | config.username, 17 | config.password, 18 | config 19 | ) 20 | } 21 | 22 | fs 23 | .readdirSync(__dirname) 24 | .filter(file => { 25 | return ( 26 | file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js' 27 | ) 28 | }) 29 | .forEach(file => { 30 | var model = sequelize['import'](path.join(__dirname, file)) 31 | db[model.name] = model 32 | }) 33 | 34 | Object.keys(db).forEach(modelName => { 35 | if (db[modelName].associate) { 36 | db[modelName].associate(db) 37 | } 38 | }) 39 | 40 | db.sequelize = sequelize 41 | db.Sequelize = Sequelize 42 | 43 | module.exports = db 44 | -------------------------------------------------------------------------------- /data/resolvers.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { User } = require('../models') 4 | const bcrypt = require('bcrypt') 5 | const jsonwebtoken = require('jsonwebtoken') 6 | require('dotenv').config() 7 | 8 | const resolvers = { 9 | Query: { 10 | // fetch the profile of currenly athenticated user 11 | async me (_, args, { user }) { 12 | // Make sure user is logged in 13 | if (!user) { 14 | throw new Error('You are not authenticated!') 15 | } 16 | 17 | // user is authenticated 18 | return await User.findById(user.id) 19 | } 20 | }, 21 | 22 | Mutation: { 23 | // Handle user signup 24 | async signup (_, { username, email, password }) { 25 | const user = await User.create({ 26 | username, 27 | email, 28 | password: await bcrypt.hash(password, 10) 29 | }) 30 | 31 | // Return json web token 32 | return jsonwebtoken.sign( 33 | { id: user.id, email: user.email }, 34 | process.env.JWT_SECRET, 35 | { expiresIn: '1y' } 36 | ) 37 | }, 38 | 39 | // Handles user login 40 | async login (_, { email, password }) { 41 | const user = await User.findOne({ where: { email } }) 42 | 43 | if (!user) { 44 | throw new Error('No user with that email') 45 | } 46 | 47 | const valid = await bcrypt.compare(password, user.password) 48 | 49 | if (!valid) { 50 | throw new Error('Incorrect password') 51 | } 52 | 53 | // Return json web token 54 | return jsonwebtoken.sign( 55 | { id: user.id, email: user.email }, 56 | process.env.JWT_SECRET, 57 | { expiresIn: '1y' } 58 | ) 59 | } 60 | } 61 | } 62 | 63 | module.exports = resolvers 64 | --------------------------------------------------------------------------------