User authentication
Now that we can get the links saved, let's add a layer of security by using jsonwebtoken.
There are 2 important things you should always do:
Hash password before storing them in the database. If you leak them, you won't expose the actual passwords.

Only store enough information in the JWT to identify the user. Anyone can read its content.
Try it yourself, take this token and paste it in jwt.io.
eyJzZWNyZXQiOiIxMjMiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.OZ-CLQhIwvruoziWza1XEB4PwCNfMnlcljRt14_Zckw

Never store password in plain text.
Never put sensitive data in a JWT.
Install the required dependencies:
$ yarn add jsonwebtoken bcryptjs @types/jsonwebtoken @types/bcryptjs
We first going to create a GraphQL input to structure the data we receive:
import { InputType, Field } from "type-graphql";
@InputType()
class UserAuthInput {
@Field()
emailAddress: string;
@Field()
password: string;
}
export { UserAuthInput };
Update the User
entity to return the jwt
with:
class User extends Model<User> {
// ...
@Field({ nullable: true })
jwt?: string;
}
Add your jwtSecret
to the shared config:
export interface SharedConfig {
jwtSecret: string;
// ...
}
export const sharedConfig: SharedConfig = {
// ...
jwtSecret: process.env.JWT_SECRET || "",
// ...
};
And finally create the resolver:
import { Resolver, Query, Mutation, Arg } from "type-graphql";
import * as bcrypt from "bcryptjs";
import { User } from "../../../../entities";
import { getUserEntity } from "../../../../containers";
import { UserAuthInput } from "./UserAuthInput";
import * as jwt from "jsonwebtoken";
import { config } from "../../../../config";
@Resolver(of => User)
class UserResolver {
private readonly _UserEntity = getUserEntity();
@Mutation(returns => User)
async signIn(@Arg("input")
{
emailAddress,
password
}: UserAuthInput): Promise<User> {
const user = await this._UserEntity.findOne({ where: { emailAddress } });
if (!user) {
throw new Error("User not found");
}
if (!bcrypt.compare(password, user.password)) {
throw new Error("Invalid password");
}
user.jwt = jwt.sign({ id: user.id }, config.jwtSecret);
return user;
}
@Mutation(returns => User)
async signUp(@Arg("input")
{
emailAddress,
password
}: UserAuthInput): Promise<User> {
if (await this._UserEntity.findOne({ where: { emailAddress } })) {
throw new Error("User with this email address already exists.");
}
const salt = bcrypt.genSaltSync(10);
try {
const user = await this._UserEntity.create({
emailAddress,
password: bcrypt.hashSync(password, salt)
});
user.jwt = jwt.sign({ id: user.id }, config.jwtSecret);
return user;
} catch (error) {
console.error("Cannot create user.", error);
throw error;
}
}
}
export { UserResolver };
Writing tests
Update setupTests.ts
to require the environment variables:
require('dotenv').config()
When building the schema, GraphQL will complain if we don't have any queries. For this reason, let's create a Health
resolver and add it to our schema when testing.
import { Int, ObjectType, Query, Resolver, Field } from "type-graphql";
@ObjectType()
class Health {
@Field(() => Int)
ok: number;
}
@Resolver(of => Health)
class HealthResolver {
@Query(returns => Health)
async health(): Promise<Health> {
return { ok: 1 };
}
}
export { HealthResolver };
Finally, test the resolver:
import { ApolloServerBase, gql } from "apollo-server-core";
import {
createTestClient
} from "apollo-server-testing";
import { buildSchema } from "type-graphql";
import { UserResolver } from "./User";
import { setUserEntity } from "../../../../containers";
import { User } from "../../../../entities";
import { HealthResolver } from "..";
import * as jwt from 'jsonwebtoken'
describe("routers/GraphQL/Resolvers/User", () => {
describe("mutation/signIn", () => {
it("should return a jwt for this user", async () => {
const schema = await buildSchema({
resolvers: [HealthResolver, UserResolver]
});
const server = new ApolloServerBase({ schema });
const testClient = createTestClient(server);
const user = { emailAddress: "[email protected]", password: "123" };
const findOne = jest.fn();
findOne.mockResolvedValue({ ...user, id: 'userid', });
setUserEntity(({ findOne } as unknown) as typeof User);
const { data, errors } = await testClient.mutate({
mutation: gql`
mutation signIn($input: UserAuthInput!) {
signIn(input: $input) {
id
emailAddress
jwt
}
}
`,
variables: { input: user }
});
expect(errors).toBeUndefined();
expect(data!.signIn).toMatchObject({ id: "userid", emailAddress: user.emailAddress })
expect(jwt.decode(data!.signIn.jwt)).toMatchObject({ id: 'userid' })
});
});
describe("mutation/signUp", () => {
it("should create a user and return the jwt", async () => {
const schema = await buildSchema({
resolvers: [HealthResolver, UserResolver]
});
const server = new ApolloServerBase({ schema });
const testClient = createTestClient(server);
const user = { emailAddress: "[email protected]", password: "123" };
const findOne = jest.fn();
const create = jest.fn();
create.mockResolvedValue({ id: "userid", ...user });
findOne.mockResolvedValue(null);
setUserEntity(({ findOne, create } as unknown) as typeof User);
const { data, errors } = await testClient.mutate({
mutation: gql`
mutation signUp($input: UserAuthInput!) {
signUp(input: $input) {
id
emailAddress
jwt
}
}
`,
variables: { input: user }
});
expect(errors).toBeUndefined();
expect(data!.signUp).toMatchObject({ id: "userid", emailAddress: user.emailAddress })
expect(jwt.decode(data!.signUp.jwt)).toMatchObject({ id: 'userid' })
});
});
});
Adding security to the links
How do we know which user just sent a request? GraphQL uses context
to pass down information to all resolvers and we will use the Authorization
header to send the JWT which each request.
Populating the GraphQL context
import "reflect-metadata";
import { ApolloServer } from "apollo-server-express";
import { Router } from "express";
import { buildSchema } from "type-graphql";
import * as resolvers from "./resolvers";
import * as jwt from "jsonwebtoken";
import { config } from "../../config";
import { User } from "../../entities";
export interface GraphQLContext {
user?: User;
}
export class GraphQLRouter {
public readonly router = Router();
constructor() {
this.buildSchema();
}
async buildSchema() {
const schema = await buildSchema({
resolvers: Object.values(resolvers),
validate: false
});
const apolloServer = new ApolloServer({
schema,
context: ({ req }): GraphQLContext => {
if (!req.headers.authorization) {
return {};
}
if (req.headers.authorization.slice(0, 7).trim() !== "Bearer") {
return {};
}
try {
return {
user: jwt.verify(
req.headers.authorization.slice(7),
config.jwtSecret
) as User
};
} catch (error) {
return {};
}
}
});
apolloServer.applyMiddleware({ app: this.router, path: "/" });
}
}
Testing
We can mock the context when creating the test server:
const server = new ApolloServerBase({
schema,
context: { user: { id: "userid" } }
});
Getting the context from resolvers
Add the argument @Ctx() context
to a resolver:
@Mutation(returns => Link)
async addLink(
@Arg("input") input: LinkInput,
@Ctx() context: GraphQLContext
): Promise<Link> {
if (!context.user) {
throw new Error("Unauthorised");
}
const link = await this._LinkEntity.create({
...input,
userId: context.user.id
});
return link;
}
Adding headers in the playground
Use the tab HTTP HEADERS
at the bottom left.

Conclusion
The backend is now ready to interact with the frontend. We can interact safely with the database and we have enough to implement anything we might need.
A lot is going on in this chapter. Have look at this Pull Request to see all the changes with comments.
Last updated
Was this helpful?