Last active
July 3, 2021 13:12
-
-
Save rossdeane/4634efd2f07a65e1be2f5b2864820213 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}`) | |
}) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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