Skip to content

Instantly share code, notes, and snippets.

@pedroraft
Last active October 29, 2022 08:21
Show Gist options
  • Save pedroraft/395c442fd980a9514e03384c60cfb773 to your computer and use it in GitHub Desktop.
Save pedroraft/395c442fd980a9514e03384c60cfb773 to your computer and use it in GitHub Desktop.
full graphql jwt auth example
JWT_SECRET=secret
import {GraphQLServerLambda} from 'graphql-yoga'
import {SchemaDirectiveVisitor} from 'graphql-tools'
import {defaultFieldResolver} from 'graphql'
import jwt from 'jsonwebtoken'
require('dotenv').config();
const typeDefs = `
directive @auth(roles: [String]) on QUERY | FIELD
type Query {
hello(name: String): String!
secret: String! @auth
admin: String @auth(roles: "admin")
reallySecret: String @auth(roles: ["kgb", "007"])
}
`
const resolvers = {
Query: {
hello: (_, {name}) => `Hello ${name || 'World'}`,
secret: (_, {}, context) => 'shhhh',
admin: (_, {}, context) => 'admin',
reallySecret: (_, {}, context) => 'super secret agent man'
}
}
class AuthDirective extends SchemaDirectiveVisitor {
visitFieldDefinition(field) {
const {resolve = defaultFieldResolver} = field
const {roles: expectedRoles = []} = this.args
field.resolve = (...args) => {
const [, , context] = args
if (context.jwt) {
if (expectedRoles.length <= 0 ||
expectedRoles.some(r => context.jwt.roles.includes(r))
) {
// Call original resolver if role check has passed
return resolve.apply(this, args)
}
}
// We has two options here. throw an error or return null (if field is nullable).
throw new Error(
`You are not authorized. Expected roles: ${expectedRoles.join(', ')} for field ${field.name}`,
)
}
}
}
const makeContext = (req) => {
const token = req.event.headers.Authorization;
if (!token) {
return {}
}
const decoded = jwt.verify(
token.replace('Bearer ', ''),
process.env.JWT_SECRET
);
return {jwt: {...decoded}}
}
const lambda = new GraphQLServerLambda({
typeDefs,
resolvers,
schemaDirectives: {auth: AuthDirective,},
context: req => ({...makeContext(req)})
})
exports.server = lambda.graphqlHandler
exports.playground = lambda.playgroundHandler
{
"name": "lambda",
"version": "1.0.0",
"description": "",
"main": "handler.js",
"scripts": {
"start": "serverless offline",
"deploy": "serverless deploy",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "Pedro raft <pedroraft@gmail.com>",
"license": "MIT",
"dependencies": {
"dotenv": "^5.0.1",
"graphql-tools": "^2.24.0",
"graphql-yoga": "*",
"jsonwebtoken": "^8.2.1",
"jwt-decode": "^2.2.0"
},
"devDependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.3",
"babel-plugin-source-map-support": "^2.0.1",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.6.0",
"babel-preset-stage-3": "^6.24.1",
"eslint": "^4.19.1",
"eslint-config-airbnb-base": "^12.1.0",
"eslint-plugin-import": "^2.11.0",
"imports-loader": "^0.8.0",
"serverless": "1.25.0",
"serverless-offline": "3.16.0",
"serverless-webpack": "^5.1.1",
"webpack": "^4.5.0",
"webpack-node-externals": "^1.7.2"
}
}
service: yoga-example
plugins:
- serverless-webpack
- serverless-offline
custom:
webpack:
webpackConfig: 'webpack.config.js'
dynamodb:
start:
port: 8000
inMemory: true
migrate: true
seed: true
provider:
name: aws
runtime: nodejs6.10
functions:
graphql:
handler: src/handler.server
events:
- http:
path: /
method: post
cors: true
playground:
handler: src/handler.playground
events:
- http:
path: /
method: get
cors: true
const path = require('path');
const slsw = require('serverless-webpack');
const nodeExternals = require('webpack-node-externals');
module.exports = {
entry: slsw.lib.entries,
target: 'node',
mode: slsw.lib.webpack.isLocal ? 'development' : 'production',
optimization: {
// We no not want to minimize our code.
minimize: false
},
performance: {
// Turn off size warnings for entry points
hints: false
},
devtool: 'nosources-source-map',
externals: [nodeExternals()],
module: {
rules: [{
test: /\.js$/,
use: [
'imports-loader?graphql',
{
loader: 'babel-loader',
options: {
presets: [
["env", {"node": "6.10"}],
"stage-3"
],
plugins: [
"source-map-support",
"transform-runtime"
]
}
},
],
}],
},
output: {
libraryTarget: 'commonjs2',
path: path.join(__dirname, '.webpack'),
filename: '[name].js',
sourceMapFilename: '[file].map'
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment