Skip to content

Instantly share code, notes, and snippets.

@rossdeane
Last active July 3, 2021 13:12
Show Gist options
  • Save rossdeane/4634efd2f07a65e1be2f5b2864820213 to your computer and use it in GitHub Desktop.
Save rossdeane/4634efd2f07a65e1be2f5b2864820213 to your computer and use it in GitHub Desktop.
import { ApolloError, ApolloServer } from 'apollo-server-express'
import express from 'express'
import neo4j from 'neo4j-driver'
import { Neo4jGraphQL } from '@neo4j/graphql'
import dotenv from 'dotenv'
import { verifyAccessToken, signAccessToken } from './tokenUtils'
import { typeDefs } from './graphql-schema'
// set environment variables from .env
dotenv.config()
const app = express()
/*
* Create a Neo4j driver instance to connect to the database
* using credentials specified as environment variables
* with fallback to defaults
*/
const driver = neo4j.driver(
process.env.NEO4J_URI || 'bolt://neo4j:7687',
neo4j.auth.basic(
process.env.NEO4J_USER || 'neo4j',
process.env.NEO4J_PASSWORD || 'neo4j'
)
)
/*
* Create an executable GraphQL schema object from GraphQL type definitions
* including autogenerated queries and mutations.
* Read more in the docs:
* https://neo4j.com/docs/graphql-manual/current/
*/
const neoSchema = new Neo4jGraphQL({
typeDefs,
driver,
config: {
jwt: {
// make sure this is the same key you use to sign the newToken below
secret: process.env.JWT_SECRET,
},
},
})
/*
* Create a new ApolloServer instance, serving the GraphQL schema
* created using makeAugmentedSchema above and injecting the Neo4j driver
* instance into the context object so it is available in the
* generated resolvers to connect to the database.
*/
const server = new ApolloServer({
// Inject the request into the context so it can be used for auth
context: ({ req }) => {
return {
req,
driver,
driverConfig: { database: process.env.NEO4J_DATABASE || 'neo4j' },
}
},
schema: neoSchema.schema,
introspection: true,
playground: true,
})
// Specify host, port and path for GraphQL endpoint
const port = process.env.GRAPHQL_SERVER_PORT || 4001
const path = process.env.GRAPHQL_SERVER_PATH || '/graphql'
const host = process.env.GRAPHQL_SERVER_HOST || 'localhost'
var authMiddleware = function (req, _, next) {
verifyAccessToken(req)
.then(async (token) => {
if (token && token.payload) {
// We've already verifyed and decoded the key, but we need to re-encode it so that neo4j-graphql can re-de-encode it!
signAccessToken(token.payload, process.env.JWT_SECRET).then(
(newToken) => {
// Set the auth header to the new token
req.headers['authorization'] = `Bearer ${newToken}`
next()
}
)
} else {
// If theres no token then just pass it through, so that we can still load the playground etc.
next()
}
})
.catch((err) => {
console.log(err)
return new ApolloError('Server Error')
})
}
/*
* Apply Express middleware for authentication
* This also also allows us to specify a path for the GraphQL endpoint
*/
app.use(authMiddleware)
server.applyMiddleware({ app, path })
app.listen({ host, port, path }, () => {
console.log(`GraphQL server ready at http://${host}:${port}${path}`)
})
import { Issuer } from 'openid-client'
import { jwtVerify } from 'jose/jwt/verify'
import { createRemoteJWKSet } from 'jose/jwks/remote'
import { SignJWT } from 'jose/jwt/sign'
const verifyAccessToken = async (request) => {
let token = request.headers['authorization']
if (!token || !token.startsWith('Bearer ')) return {}
// Remove 'Bearer ' characters from start of Auth header value
token = token.slice(7, token.length).trimLeft()
const settings = {
audience: 'api://' + process.env.AZURE_CLIENT_ID || '',
openIdConfigUrl: process.env.AZURE_OPENID_METADATA || '',
tenantId: process.env.AZURE_TENANT_ID || '',
}
// get azure issuer details
const issuer = await Issuer.discover(settings.openIdConfigUrl)
// create a JWKS from above fetched details
const JWKS = createRemoteJWKSet(new URL(issuer.metadata.jwks_uri))
// decode JWT and ensure the aud claim is correct
var results = jwtVerify(token, JWKS, {
audience: settings.audience,
})
return results
}
const signAccessToken = async (payload, signingKey) => {
//Jose wants the signing key to be in this format, not just a string
const signingKeyBuffer = Buffer.from(signingKey, 'utf8')
return new SignJWT(payload)
.setProtectedHeader({ alg: 'HS256' })
.sign(signingKeyBuffer)
}
export { verifyAccessToken, signAccessToken }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment