Lately, I've been working on building one of the app challenges on devchallenges.io.
I decided to use Next.js with GraphQL as my stack. I was a little worried about how I would implement secure HTTP-only authentication but it turned out to be super simple! Let me show you how.
Starting off we'll use a basic graphql API route adapted from the next.js example
import { ApolloServer, gql } from 'apollo-server-micro'
const typeDefs = gql`
type Query {
me: User
}
type Mutation {
signup(username: String!, password: String!): User
}
type User {
username: String!
}
`
const resolvers = {
Query: {
me(_parent, _args, context) {
// what do we do here?
},
},
Mutation: {
signup(_parent, {username, password}, context) {
// ??
},
}
}
const apolloServer = new ApolloServer({ typeDefs, resolvers })
export const config = {
api: {
bodyParser: false,
},
}
export default apolloServer.createHandler({ path: '/api/graphql' })
Here's where the fun begins.
We'll import jsonwebtoken
and cookies
(make sure you add them to your dependencies!):
import jwt from "jsonwebtoken";
import Cookies from "cookies";
Then we'll add a context within the apollo server where we'll create a cookie jar to set and get cookies within our resolves and parse our JWT token (if we have it).
const verifyToken = (token) => {
if (!token) return null;
try {
return jwt.verify(token, process.env.SECRET!);
} catch {
return null;
}
};
const apolloServer = new ApolloServer({
typeDefs,
resolvers,
context: ({ req, res }) => {
const cookies = new Cookies(req, res);
const token = cookies.get("auth-token");
const user = verifyToken(token);
return {
cookies,
user,
};
},
});
Now in our resolvers, we can set the cookie when a user signs up (and signs in, but I'll let you figure that out):
const resolvers = {
// ...
Mutation: {
async signup(_parent, {username, password}, context) {
let hash = await bcrypt.hash(password, 10);
// bring your own db logic
let user = await db.createUser({username, password: hash})
let token = jwt.sign({ id: user.id }, process.env.SECRET!);
context.cookies.set("auth-token", token, {
httpOnly: true,
sameSite: "lax",
// here we put 6 hours, but you can put whatever you want (the shorter the safer, but also more annoying)
maxAge: 6 * 60 * 60,
secure: process.env.NODE_ENV === "production",
});
return user;
},
}
}
Now, whenever a request is made to check our auth status, it's easy!
const resolvers = {
Query: {
me(_parent, _args, context) {
// bring your own db logic
context.user?.id ? db.findUser(context.user.id) : null
},
},
}
That should be enough to get you started 😄
Top comments (1)
Thanks for the article I'm looking to start working on the dev challenges projects can please show us on how you handled this on the client with apollo client 3?