[ PART 4 ] Creating a Twitter clone with GraphQL, Typescript, and React ( User Login )
Tags: showdev, career, node, javascript
Published: 2021-01-07 10:57:43

[Github Repo](https://github.com/ips-coding-challenge/challenge_twitter)

Hi everyone. I hope I'm not tiring you out too much:D 

Let's continue our project with the connection of a user.

*src/resolvers/AuthResolver.ts*

```typescript
@Mutation(() => AuthResponse)
  async login(@Arg('input') input: LoginPayload, @Ctx() ctx: MyContext) {
    const { db } = ctx

    const [user] = await db('users').where('email', input.email)

    if (!user) {
      throw new ApolloError('Invalid credentials')
    }

    const isPasswordValid = await argon2.verify(user.password, input.password)

    if (!isPasswordValid) {
      throw new ApolloError('Invalid credentials')
    }

    const token = generateToken(user)

    return { token, user }
  }
```
Nothing new here. I'm checking if I have a user corresponding to the email. Then I check if the passwords match and if everything is good, I generate a JWT token that I send back with the user.

As for the **LoginPayload** class which contains the validation rules, here it is:

*src/dto/LoginPayload.ts*

```typescript
import { IsEmail } from 'class-validator'
import { Field, InputType } from 'type-graphql'

@InputType()
class LoginPayload {
  @Field()
  @IsEmail()
  email: string

  @Field()
  password: string
}

export default LoginPayload

```

Here is the result in the GraphQL playground
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/q4z4s9ngimt4ykhxki0p.png)

Let's still write some tests ;)

*src/tests/auth.test.ts*

```typescript
it('should log in a user', async () => {
  await createUser()
  const { mutate } = await testClient()

  const res = await mutate({
    mutation: LOGIN,
    variables: {
      input: {
        email: 'admin@test.fr',
        password: 'password',
      },
    },
  })

  const { token, user } = res.data.login
  expect(token).not.toBeNull()
  expect(user.username).toEqual('admin')
  expect(user.email).toEqual('admin@test.fr')
})

it('should throw a validation error if the email is invalid', async () => {
  await createUser()
  const { mutate } = await testClient()

  const res = await mutate({
    mutation: LOGIN,
    variables: {
      input: {
        email: 'adminaz',
        password: 'password',
      },
    },
  })

  expect(res.data).toBeNull()
  expect(res.errors).not.toBeNull()
  const {
    extensions: {
      exception: { validationErrors },
    },
  }: any = res.errors![0]

  expect((validationErrors[0] as ValidationError).constraints).toEqual({
    isEmail: 'email must be an email',
  })
})

it('should throw a validation error if the password is empty', async () => {
  await createUser()
  const { mutate } = await testClient()

  const res = await mutate({
    mutation: LOGIN,
    variables: {
      input: {
        email: 'admin@test.fr',
        password: '',
      },
    },
  })

  expect(res.data).toBeNull()
  expect(res.errors).not.toBeNull()
  const {
    extensions: {
      exception: { validationErrors },
    },
  }: any = res.errors![0]

  expect((validationErrors[0] as ValidationError).constraints).toEqual({
    isNotEmpty: 'password should not be empty',
  })
})
```

## Auth middleware

**type-graphql** has an authChecker option that can be passed to the *buildSchema()* method.

*src/server.ts*

```typescript
import 'reflect-metadata'
import { ApolloServer } from 'apollo-server'
import { buildSchema } from 'type-graphql'
import AuthResolver from './resolvers/AuthResolver'
import db from './db/connection'
import { authChecker } from './middlewares/authChecker'

const createServer = async () => {
  return new ApolloServer({
    schema: await buildSchema({
      resolvers: [AuthResolver],
      authChecker: authChecker,
    }),
    context: ({ req, res }) => {
      return {
        req,
        res,
        db,
      }
    },
  })
}

export default createServer

```

And it is in this function that we are going to check if we have an authenticated user.

*src/middlewares/authChecker.ts*

```typescript
import { AuthChecker } from 'type-graphql'
import { MyContext } from '../types/types'
import { extractJwtToken } from '../utils/utils'
import jwt from 'jsonwebtoken'
import { JWT_SECRET } from '../config/config'

export const authChecker: AuthChecker<MyContext, string> = async ({
  root,
  args,
  context,
  info,
}) => {
  const { db, req } = <MyContext>context

  try {
    const token = extractJwtToken(req)
    const {
      data: { id },
    }: any = jwt.verify(token, JWT_SECRET as string)

    const [user] = await db('users').where('id', id)

    if (!user) throw new AuthenticationError('User not found')

    context.userId = user.id
    return true
  } catch (e) {
    throw e
  }
}

```

The *extractJwtToken()* function just allows us to check that we have a header Authorization with a Bearer token. I let you check the [Repository Github](https://github.com/ips-coding-challenge/challenge_twitter/blob/main/src/utils/utils.ts)

To use this **authChecker** we just need to annotate the method with *@Authorized*.

*src/resolvers/AuthResolver.ts*

```typescript
@Query(() => User)
@Authorized()
async me(@Ctx() ctx: MyContext) {
    const { db, userId } = ctx
    const [user] = await db('users').where('id', userId)

    return user
}
```

If I try now without setting the "Authorization" header
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/hu1le96rrtegm0nyu45o.png)

And with the JWT Token in the Authorization header
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/hh3oomwnln12o1c57bci.png)

Everything works as expected ;)

Ciao and take care! See you in the next part ^^