Backend Setup
#
Complete Quick SetupComplete both steps from the quick setup guide for SuperTokens:
CAUTION
This guide only applies to scenarios which involve SuperTokens Session Access Tokens.
If you are implementing either, Unified Login or Microservice Authentication, features that make use of OAuth2 Access Tokens, please check the separate page that shows you how to verify those types of tokens.
This should give you a login UI, backend APIs for the login UI to work, session refreshing and signout. The rest of this guide will focus on how you can do session verification in your GraphQL resolvers.
#
Method 1: Session verification in the GraphQL context (Easier)We want to use the Session.getSession
function in the context
function to verify the session, and add the userId
into our context so that our resolvers can read it. If the user id not logged in, we will set the userId
to undefined
in the context
import { ApolloServer } from "@apollo/server";
import express from "express";
import { expressMiddleware } from "@apollo/server/express4";
import { GraphQLError } from 'graphql';
import Session from "supertokens-node/recipe/session";
let app = express();
const typeDefs = '...'
const resolvers = {/* ... */ }
const server = new ApolloServer({
typeDefs,
resolvers,
})
server.start().then(() => {
app.use(express.json(), expressMiddleware(server, {
// Note: This example uses the `req` and `res` argument to access headers,
// but the arguments received by `context` vary by integration.
// This means they vary for Express, Fastify, Lambda, etc.
context: async ({ req, res }) => {
try {
let session = await Session.getSession(req, res, {
sessionRequired: false
})
return {
userId: session !== undefined ? session.getUserId() : undefined
};
} catch (err) {
if (Session.Error.isErrorFromSuperTokens(err)) {
throw new GraphQLError('Session related error', {
extensions: {
code: 'UNAUTHENTICATED',
http: { status: err.type === Session.Error.INVALID_CLAIMS ? 403 : 401 },
},
});
}
throw err;
}
},
}))
app.listen(3001, () => {
console.log("Server started");
})
})
In the above code snippet, we first attempt to verify the session using the Session.getSession
function. If the session is valid, we will add the userId
to the context. If the access token has expired, we will throw an error with a status code of 401
. If a session claim has failed (for example if the user's email is not verified) we will return a status code of 403
.
The 401
status code will cause the session refresh flow to start, which will give a new access token to the user, or else if the session was revoked, the user will be logged out.
In case the user is not logged in, the Session.getSession
function will throw return undefined
, in which case, your resolvers won'd have a userId
in the context.
The downside of this method is that if you want to mutate the session's access token payload in one of your resolvers, then you don't have access to the session
object in there. This is where the method below comes into the picture:
#
Method 2: Session verification in the graqphQL resolver (More flexible)Unlike the method above, we will be doing session verification on a per resolver basis here. This means that you will have access to the session
object in your resolver using which you can update the information in the session (like its access token payload).
We start by creating a helper function (a sort of middleware for your resolver) which you will have to call in all of your resolvers that require a session:
import Session, { SessionContainer } from "supertokens-node/recipe/session";
import { GraphQLError } from 'graphql';
async function withSession<T>(contextValue: any, resolver: (session: SessionContainer) => Promise<T>) {
try {
let session = await Session.getSession(contextValue.req, contextValue.res);
return await resolver(session);
} catch (err) {
if (Session.Error.isErrorFromSuperTokens(err)) {
throw new GraphQLError('Session related error', {
extensions: {
code: 'UNAUTHENTICATED',
http: { status: err.type === Session.Error.INVALID_CLAIMS ? 403 : 401 },
},
});
}
}
}
In the above function, we attempt to verify the session using Session.getSession
. If the session is valid, we will call the resolver
function with the session
object. If the access token has expired, or if the session does not exist, we will throw an error with a status code of 401
. If a session claim has failed (for example if the user's email is not verified) we will return a status code of 403
.
For this resolver to work, we will have to add the req
and res
object into the GraphQL context. This can be done as follows:
import { ApolloServer } from "@apollo/server";
import express from "express";
import { expressMiddleware } from "@apollo/server/express4";
import { GraphQLError } from 'graphql';
let app = express();
const typeDefs = '...'
const resolvers = {/* ... */}
const server = new ApolloServer({
typeDefs,
resolvers,
})
server.start().then(() => {
app.use(express.json(), expressMiddleware(server, {
// Note: This example uses the `req` and `res` argument to access headers,
// but the arguments received by `context` vary by integration.
// This means they vary for Express, Fastify, Lambda, etc.
context: async ({req, res}) => {
return {
req, res
};
},
}))
app.listen(3001, () => {
console.log("Server started");
})
})
Finally, we can use our withSession
in our resolvers as shown below:
import { ApolloServer } from "@apollo/server";
const server = new ApolloServer({
typeDefs,
resolvers: {
Query: {
userProfile: async (_: any, __: any, contextValue) => {
// starts of your resolver code..
return await withSession(contextValue, async (session) => {
// getUserName is a custom application function...
let name = await getUserName(session.getUserId())
return {
userId: session.getUserId(),
sessionHandle: session.getHandle(),
name
};
});
}
},
},
})