Skip to content

Instantly share code, notes, and snippets.

@lfades
Created October 11, 2018 19:06
Show Gist options
  • Save lfades/633e503f26f3f8ade2f4fa557db3a931 to your computer and use it in GitHub Desktop.
Save lfades/633e503f26f3f8ade2f4fa557db3a931 to your computer and use it in GitHub Desktop.
Next.js Auth implementation: Apollo Server + Passport + Passport-jwt
/**
* client/pages/_app.js
* Implementation of the withApollo HOC in _app.js, this makes Apollo available in every page
*/
import App, { Container } from 'next/app';
import { ApolloProvider } from 'react-apollo';
import withApollo from '../lib/withApollo';
class MyApp extends App {
render() {
const { Component, pageProps, apollo } = this.props;
return (
<Container>
<ApolloProvider client={apollo}>
<Component {...pageProps} />
</ApolloProvider>
</Container>
);
}
}
export default withApollo(MyApp);
/**
* server/app.js
* Apollo Server implementation
*/
const path = require('path');
const { ApolloServer } = require('apollo-server');
const { importSchema } = require('graphql-import');
const cookieParser = require('cookie-parser');
const { APP_URL } = require('./configs');
const resolvers = require('./resolvers');
const schemaDirectives = require('./schemaDirectives');
const dataSources = require('./dataSources');
const Auth = require('./dataSources/Auth');
const typeDefs = importSchema(
path.join(__dirname, './typeDefs/Schema.graphql')
);
/**
* Create our instance of cookieParser and a function (addCookies) to use it as
* a Promise instead of a middleware in express
*/
const cp = cookieParser();
const addCookies = (req, res) =>
new Promise(resolve => {
cp(req, res, resolve);
});
const server = new ApolloServer({
typeDefs,
resolvers,
schemaDirectives,
dataSources,
cors: {
origin: APP_URL,
credentials: true,
optionsSuccessStatus: 200,
methods: ['POST']
},
playground: {
settings: {
// possible values: 'line', 'block', 'underline'
'editor.cursorShape': 'block',
'request.credentials': 'include'
}
},
context: async ({ req, res }) => {
const auth = new Auth({ req, res });
await addCookies(req, res);
await auth.authenticate();
return { auth };
}
});
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
/**
* server/dataSources/Auth.js
* Auth implementation, it's not really a dataSource so it doesn't need to be here
*/
const { authenticate, createJwt } = require('../lib/passport');
const { ON_HTTPS } = require('../configs');
const ONE_MINUTE = 1000 * 60;
const ONE_DAY = ONE_MINUTE * 60 * 24;
const ONE_MONTH = ONE_DAY * 30;
class Auth {
constructor({ req, res }) {
this.req = req;
this.res = res;
this.isReady = false;
this.hasSignedIn = false;
this.accessTokenName = 'ip_at';
}
async authenticate() {
const { req, res } = this;
// The token is very likely to be in cookies
if (!req.headers.authorization) {
const cookie = req.cookies[this.accessTokenName];
if (cookie) req.headers.authorization = `bearer ${cookie}`;
}
const payload = await authenticate(req, res);
if (payload) {
this.payload = payload;
this.hasSignedIn = true;
}
}
signInWithJWT(user) {
const token = createJwt({ uid: user.id });
this.res.cookie(this.accessTokenName, token, {
secure: ON_HTTPS,
httpOnly: true,
expires: new Date(Date.now() + ONE_MONTH)
});
}
logout() {
this.res.clearCookie(this.accessTokenName);
}
getUserId() {
return this.payload.uid;
}
}
module.exports = Auth;
/**
* server/lib/passport.js
* Passport.js + passport-jwt implementation, it's really simple and based in a httpOnly cookie that lasts for 1 month
*/
const jwt = require('jsonwebtoken');
const passport = require('passport');
const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt');
const { JWT_SECRET } = require('../configs');
const verifyOptions = {
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: JWT_SECRET,
audience: 'inspector.com',
algorithms: ['HS256']
};
passport.use(
new JwtStrategy(verifyOptions, (payload, done) => done(null, payload))
);
exports.createJwt = payload =>
jwt.sign(payload, JWT_SECRET, {
algorithm: 'HS256',
audience: verifyOptions.audience,
expiresIn: '30 days'
});
exports.authenticate = (req, res) =>
new Promise((resolve, reject) => {
passport.authenticate('jwt', (err, payload) => {
if (err) reject(err);
resolve(payload);
})(req, res);
});
/**
* client/lib/withApollo.js
* Apollo Client implementation
*/
import withApollo from 'next-with-apollo';
import ApolloClient from 'apollo-boost';
export default withApollo(
({ headers }) =>
new ApolloClient({
uri: process.API_URL,
credentials: 'include',
headers
})
);
@lfades
Copy link
Author

lfades commented Jul 25, 2019

You can add it to a Node.js server and that's it, Next.js has nothing to do there 👍

@cantoute
Copy link

for newbies like me, an example of the configs.js would be nice. Thx

@lfades
Copy link
Author

lfades commented Aug 20, 2019

@cantoute that file does something like this:

exports.JWT_SECRET = process.env.JWT_SECRET

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment