Since the JWT token comes in a header, we want to handle context in ApolloServer
.
const server = new ApolloServer({
// ...
context: async ({ event: { headers } }): Promise<OperationContext> => {
let userId = null
try {
userId = await userIdFromToken(
headers["Authorization"].replace("Bearer ", "")
)
} catch {}
return {
userId,
}
},
})
We take Authorization
from the header, remove the "Bearer" word, and pass it to the userIdFromToken
function.
It takes a secret from env variables. Then we verify the token against the secret with jwt.verify.
import jwt from "jsonwebtoken"
import { assertEnvVar } from "../shared/assertEnvVar"
interface DecodedToken {
id: string
}
export const userIdFromToken = async (token: string) => {
const secret = assertEnvVar("SECRET")
const decoded = jwt.verify(token, secret)
return decoded ? (decoded as DecodedToken).id : null
}
If everything is alright, we should have an id
in a decoded result.
Now, we should get the user id in the resolver as a part of the context argument.
export interface OperationContext {
userId: string | null
country: string | null
}
To check for userId, I run the assertUserId
function. It will throw an error if the user id is missing.
import { AuthenticationError } from "apollo-server-lambda"
import { OperationContext } from "../graphql/OperationContext"
export const assertUserId = ({ userId }: OperationContext) => {
if (!userId) {
throw new AuthenticationError("Invalid token")
}
return userId
}
The API creates a token on the authorization request and sends it back to the user.
Here we have the generateAuthData
function that takes the user id. It calculates token expiration time and signs the token with the secret from env variables.
import jwt from "jsonwebtoken"
import { assertEnvVar } from "../shared/assertEnvVar"
const jwtLifespanInSeconds = 15552000
const getTokenExpirationTime = (ms: number) =>
Math.floor(Date.now() / 1000) + ms
export const generateAuthData = (id: string) => {
const tokenExpirationTime = getTokenExpirationTime(jwtLifespanInSeconds)
const secret = assertEnvVar("SECRET")
return {
token: jwt.sign({ id, exp: tokenExpirationTime }, secret),
tokenExpirationTime,
} as const
}