-
Notifications
You must be signed in to change notification settings - Fork 28.2k
Add Apollo Server and Client Auth Example #9913
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Apollo Server and Client Auth Example #9913
Conversation
@lfades let me know if you have some better abstractions around the jwt / context passing in the creation of the apollo client. wasn't 100% what to do there, but it works! |
Stats from current PRDefault Server ModeGeneral
Client Bundles (main, webpack, commons)
Client Bundles (main, webpack, commons) Modern
Legacy Client Bundles (polyfills)
Client Pages
Client Pages Modern
Client Build Manifests
Rendered Page Sizes
Serverless ModeGeneral
Client Bundles (main, webpack, commons)
Client Bundles (main, webpack, commons) Modern
Legacy Client Bundles (polyfills)
Client Pages
Client Pages Modern
Client Build Manifests
Serverless bundles
Commit: dd8e6ca |
Stats from current PRDefault Server ModeGeneral
Client Bundles (main, webpack, commons)
Client Bundles (main, webpack, commons) Modern
Legacy Client Bundles (polyfills)
Client Pages
Client Pages Modern
Client Build Manifests
Rendered Page Sizes
Serverless ModeGeneral
Client Bundles (main, webpack, commons)
Client Bundles (main, webpack, commons) Modern
Legacy Client Bundles (polyfills)
Client Pages
Client Pages Modern
Client Build Manifests
Serverless bundles
Commit: 3ff890d |
Stats from current PRDefault Server ModeGeneral
Client Bundles (main, webpack, commons)
Client Bundles (main, webpack, commons) Modern
Legacy Client Bundles (polyfills)
Client Pages
Client Pages Modern
Client Build Manifests
Rendered Page Sizes
Serverless ModeGeneral
Client Bundles (main, webpack, commons)
Client Bundles (main, webpack, commons) Modern
Legacy Client Bundles (polyfills)
Client Pages
Client Pages Modern
Client Build Manifests
Serverless bundles
Commit: 47cb334 |
Stats from current PRDefault Server ModeGeneral
Client Bundles (main, webpack, commons)
Client Bundles (main, webpack, commons) Modern
Legacy Client Bundles (polyfills)
Client Pages
Client Pages Modern
Client Build Manifests
Rendered Page Sizes
Serverless ModeGeneral
Client Bundles (main, webpack, commons)
Client Bundles (main, webpack, commons) Modern
Legacy Client Bundles (polyfills)
Client Pages
Client Pages Modern
Client Build Manifests
Serverless bundles
Commit: 5199e0b |
Stats from current PRDefault Server ModeGeneral
Client Bundles (main, webpack, commons)
Client Bundles (main, webpack, commons) Modern
Legacy Client Bundles (polyfills)
Client Pages
Client Pages Modern
Client Build Manifests
Rendered Page Sizes
Serverless ModeGeneral
Client Bundles (main, webpack, commons)
Client Bundles (main, webpack, commons) Modern
Legacy Client Bundles (polyfills)
Client Pages
Client Pages Modern
Client Build Manifests
Serverless bundles
Commit: 1e8a6c5 |
Stats from current PRDefault Server ModeGeneral
Client Bundles (main, webpack, commons)
Client Bundles (main, webpack, commons) Modern
Legacy Client Bundles (polyfills)
Client Pages
Client Pages Modern
Client Build Manifests
Rendered Page Sizes
Serverless ModeGeneral
Client Bundles (main, webpack, commons)
Client Bundles (main, webpack, commons) Modern
Legacy Client Bundles (polyfills)
Client Pages
Client Pages Modern
Client Build Manifests
Serverless bundles
Commit: cf922d4 |
@lifeiscontent Currently I have mixed feelings about this example, the cookie is not I know that the current cookie auth example is like that, and that has to be fixed too before adding another not so secure auth example. |
@lfades that's fine, I don't mind updating it. This was only my first pass at the work to get it released :) |
@lfades I copied the with-cookie-auth example, but I'm actually not sure if this is something that would be considered "best practice". I'd like to understand what the best approach between requests of client / server in next.js would be. If you'd like I can strictly make the API respond to the authorization header, or make the cookie secure, but I mainly wanted to have a discussion about it with a bunch of you smart people before I do more work on it. |
@lifeiscontent thank you for your time and effort. I am adding @rkotze (he's maybe unfamiliar with next.js) that might have an interesting input. He wrote a great article about JWT and httpOnly cookies for Apollo here: https://github.com/rkotze/rkotze.github.io/blob/master/_posts/2019-12-02-jwt-secure-apollo-client-graphql.md My guess is that you should use ctx.cookie.set('accessToken', token, {
path,
httpOnly: true,
sameSite: 'Strict',
maxAge
}) In that case the I might be very wrong about everything I just wrote. I will come back after a more indepth testing. |
@raduchiriac great, yeah, I'm not 100% on the protocols for security here either I figured I just put this POC up and have smarter people than me comment on it and then I'll do the work to make it better 👍 |
@raduchiriac imao, the auth exemple should just be update with the change needed for an httpOnly Cookie use. As it's different then cookie with Next. |
@lifeiscontent Do this:
It would not be a state of the class auth method, but it's definitely better. |
@lfades sorry for the naivety here, maybe I don't understand something about how the browser/server deals with setting cookies on an HTTP level, I've updated the login resolve to work as follows: async signIn(_parent, args, context, _info) {
const user = await context.models.User.findOne({
where: { email: args.input.email },
})
if (user && user.validPassword(args.input.password)) {
const token = context.jwt.sign(
{ email: user.email, id: user.id, time: new Date() },
context.serverRuntimeConfig.JWT_SECRET,
{
expiresIn: '6h',
}
)
const expires = new Date()
expires.setTime(expires.getTime() + 6 * 60 * 60 * 1000)
const cookie = Cookie.serialize('token', token, {
expires,
httpOnly: true,
sameSite: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 6 * 60 * 60,
})
context.res.setHeader('Set-Cookie', cookie)
return { user, token }
}
throw new UserInputError('Invalid email and password combination')
} however, when I get the server for a cookie, it seems to have not been set. e.g. in auth.js: export const auth = ctx => {
console.log(ctx.req.headers); // return nothing about a cookie here?
// const { token } = cookie.parse(ctx.req.headers['cookie'])
const token = '';
// If there's no token, it means the user is not signed in.
if (!token) {
if (typeof window === 'undefined') {
ctx.res.writeHead(302, { Location: '/signin' })
ctx.res.end()
} else {
Router.push('/signin')
}
}
return token
} do you have any idea what I'm doing wrong here? |
@lifeiscontent Follow this example: #9986 - but use Apollo instead of Fauna, the logic should be very similar. |
@lfades updated! |
Stats from current PRDefault Server ModeGeneral
Client Bundles (main, webpack, commons)
Client Bundles (main, webpack, commons) Modern
Legacy Client Bundles (polyfills)
Client Pages
Client Pages Modern
Client Build Manifests
Rendered Page Sizes
Serverless ModeGeneral
Client Bundles (main, webpack, commons)
Client Bundles (main, webpack, commons) Modern
Legacy Client Bundles (polyfills)
Client Pages
Client Pages Modern
Client Build Manifests
Serverless bundles
Commit: 2b01856 |
Stats from current PRDefault Server ModeGeneral
Client Bundles (main, webpack, commons)
Client Bundles (main, webpack, commons) Modern
Legacy Client Bundles (polyfills)
Client Pages
Client Pages Modern
Client Build Manifests
Rendered Page Sizes
Serverless ModeGeneral
Client Bundles (main, webpack, commons)
Client Bundles (main, webpack, commons) Modern
Legacy Client Bundles (polyfills)
Client Pages
Client Pages Modern
Client Build Manifests
Serverless bundles
Commit: 5dfc446 |
@lifeiscontent Looks like I can't run this example in my machine, I'm missing C++ libraries to have Please add the deploy button (example here) and test it. Can you confirm that you did what I asked for? it looks like yes, I checked the files and everything looks in order. Also if you can update the readme with the prerequisites that would be awesome too, at least in Windows I know that I need to have python installed and other C++ libraries, if you can use something else that's not sequalize, that can also work. This is an example after all, the easier it can be to run the better. |
@lfades I can add a deploy button but I’m pretty sure any library that depends on a DB will require native libraries, I don’t think this is an issue with sequelize but more likely sqlite |
@lifeiscontent If the purpose of the example is to showcase Apollo Server and Auth, whatever DB you choose is not important, a fs based DB could work too. |
@lfades that's fair, so what are you suggesting? Should I add the deploy button or should I swap out sequelize for something else? |
@lifeiscontent Is your example, do it as you like, my work here is to make sure that it works properly and other people can use it. So following that statement, update the README to include good instructions for people who may have issues installing the packages, and add the deploy button and test that it works. It may be easier to run the example if you switch the db, but that's up to you. |
@lfades updated and removed sequelize. Let me know if you need anymore changes. Also, since the data is in memory, if you edit the code during |
@lfades ping |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lifeiscontent Looks good to me now, thank you, feel try to use a different storage method, the current one doesn't work well when deployed, maybe a very lightweight db?
Thanks for this awesome example, using it as a boilerplate for a project I'm working on right now. Since I had some difficulty trying to figure out how to write a middleware to parse the JWT and inject it into the Right now to get the current user in a resolver we need to do the following logic everytime:
// user resolvers
viewer: async (_parent, _args, context, _info) => {
// parse the token from the cookies
const { token } = cookie.parse(context.req.headers.cookie ?? '')
if (token) {
try {
// check if token is valid and get payload
const { id, email } = jwt.verify(token, JWT_SECRET)
// fetch user from db
return users.find(...)
} catch {
// handle error
throw new AuthenticationError('Unauthenticated')
}
}
} I wanted something like this: // user resolvers
viewer: async (_parent, _args, context, _info) => {
const { id, email } = context.user
// rest
} JWT Middleware to the rescue. // middleware.js
import cookie from 'cookie'
import jwt from 'jsonwebtoken'
import getConfig from 'next/config'
const { JWT_SECRET, JWT_ISSUER, JWT_AUDIENCE } = getConfig().serverRuntimeConfig
const jwtParser = (handler) => {
return async (req, res) => {
let user = null
const { token } = cookie.parse(req.headers.cookie ?? '')
if (token) {
try {
user = jwt.verify(
token,
JWT_SECRET,
{
expiresIn: '6h',
issuer: JWT_ISSUER,
audience: JWT_AUDIENCE
}
)
} catch {
user = null
}
}
req.user = user
return handler(req, res);
}
}
const middlewareHandler = handler => jwtParser(handler);
export default middlewareHandler; Then import this to // graphql.js
...
export default middleware(apolloHandler) Now I can access the user from the context 👇 viewer: (_parent, _args, context, _info) => {
if (!context.req.user) throw new AuthenticationError('Unauthenticated')
return context.req.user
} Here's a boilerplate project based on this example with the implementation of this logic and other juicy stuff like user roles, graphql schema refactoring, database connection middleware (mongoose) and jest tests (soon). |
Add Apollo Server and Client Auth example.
Original conversation with @lfades here #9274