Authenticating full-stack Nextjs App
Tags: nextjs
Published: 2020-11-24 10:33:17

In The Previous blog we created a **FullStack Nextjs with Typescript, Graphql**.  In this post we will learn how we add authentication to the app.  You can read the post [here](https://dev.to/imranib/fullstack-nextjs-with-typescript-graphql-2ce0).
You can get the code from this [repo](https://github.com/imran-ib/full-stack-nextjs.git) . 

If You want to see the final code you can get it from [here](https://github.com/imran-ib/auntenticating-next-app.git).

From Your terminal run this command

 `git clone https://github.com/imran-ib/full-stack-nextjs.git`

cd into `full-stack-nextjs` and run `npm install`

After the installation is finished `npm run dev`  and go to [localhost:3000](http://localhost:3000/). 

You should see something like this 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/m3oliurzq3wq752p2fa2.png)

and if you go to [localhost:3000/api](http://localhost:3000/api) you can see our graphql api 
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/6g5xrqcf2ipqnvco0jiq.png)

Let's get started Now 

in `pages/api/index.ts` file we have  two models `user`and `post` and two `resolvers` `mutations` and `query` and `schema`

let's add these to their separate files.  create three folders `Models` , `Resolvers` , `Schema` in `pages/api` directory

and add `user` and `post` to `pages/api/Models` 

`mutations` and `query` to `pages/api/Resolvers`

`schema` to `pages/api/Schema`

and import these files to `pages/api/index.ts`

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/d8dcv1dya2mtqe5oxf8a.png)
import models and resolvers to schema

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/w964urst5kmmo2zrju4o.png)
import this file `(schema.ts)` to `pages/api/index` .

**Note: Please don't forget to move all related imports to each file**

Now You App Should working file as before. 

our `pages/api/index.ts` file looks like this

```javascript
import { ApolloServer } from "apollo-server-micro";
import { schema } from "./Schema";

export const config = {
  api: {
    bodyParser: false,
  },
};

export default new ApolloServer({ schema }).createHandler({
  path: "/api",
});

```
we need to change the way we are creating server 

```javascript
const server = new ApolloServer({ schema });

export default server.createHandler({
  path: "/api",
});
```

**Note : if you have any problem or getting any error while changing files to different directories you can get the code from running following command  **

`git clone -b file-structre https://github.com/imran-ib/auntenticating-next-app.git`

after cloning the repo just install dependencies. 

# Context
context is an object or a function that creates an object.  We pass this object to our server and server will passes this context to every resolver. In this way we can share information through out our app. 

create a file named `context.ts`  in `pages/api`

```javascript
import { PrismaClient } from "@prisma/client";
import { Request, Response } from "express";
const prisma = new PrismaClient();

export interface Context {
  prisma: PrismaClient;
  req: Request;
  res: Response;
}

export const createContext = (ctx: any): Context => {
  return {
    ...ctx,
    prisma,
  };
};

```

Now Import `createContext` to our  `pages/api/index.ts` and set it to Apollo server context

```javascript
const server = new ApolloServer({
  schema,
  context: createContext,
});
```

So far so good
# graphql-shield

graphql-shield is  a sort of middleware that creates a permission layer for your application. we will add it to the schema as a middleware and it will allow us to check users permissions before resolving any function.

`npm i graphql-shield`

create new file `pages/api/permissions/index.ts` and paste following code

```javascript
import { shield } from "graphql-shield";

const rules = {
  isAuthenticatedUser: {},
};

export const permissions = shield({
  Query: {},
  Mutation: {},
});

```

`isAuthenticatedUser` :  we will set it to a Boolean so that if user is authenticated it will be true otherwise it will be false

in `permissions` we will add queries and mutations we want to protect 

Now We will add this to schema but for that we need another package `graphql-middleware`

GraphQL Middleware lets you run arbitrary code before or after a resolver is invoked.

`npm i graphql-middleware`

now import permissions and `applyMiddleware` from  the package we installed graphql middleware into `pages/api/index.ts` 

```javascript
import { ApolloServer } from "apollo-server-micro";
import { applyMiddleware } from "graphql-middleware";
import { schema } from "../../server/Schema";
import { createContext } from "./context";
import { permissions } from "./permissions";

export const config = {
  api: {
    bodyParser: false,
  },
};

const server = new ApolloServer({
  schema: applyMiddleware(schema, permissions), // <-- Changes
  context: createContext,
});

export default server.createHandler({
  path: "/api",
});

```

This is it. Graphql Shield is set up.

# JWT

Now we need to identify users with the help of  `jsonwebtoken` 

`npm i jsonwebtoken && npm i -D @types/jsonwebtoken`

jesonwebtoken normally refers as simply `jwt` . jwt is  base64url-encoded which can be easily decoded later.   it is worth mentioning that we don't put valuable information in jwt because anyone can decode it and get information. We only put information like user id which is normally random digit or a long string. We need that to identify user but if someone gets that information it is ok for us.



When user will  try to login and provides correct credentials we will generate a jwt for that user with user's id encoded in it and send it to client side. We then take that token on client side and store it in local storage. with every request we will send that token to our server. 

let's go to our `mutation.ts` file and  in  this file we four mutations 

`signupUser` , `deletePost` , `createDraft`, and `publish` 

Let's create new mutation `userLogin`

```javascript
 t.field("userLogin", {
      type: "String",
      args: {
        email: stringArg(),
        password: stringArg(),
      },
      description: "User Sign in",
      resolve: async (parent, args, ctx) => {
        try {
          const User = await prisma.user.findOne({
            where: {
              email: args.email,
            },
          });

          if (!User) return new Error(`Authorization failed`);
          const token = jwt.sign({ UserId: User.id }, "MyNewApp");
          return token;
        } catch (error) {
          console.log("definition -> error", error);
        }
      },
    });
```

**NOTE:  ideally you would want to hash the password when creating user and then compare that hash when you letting them in, but in this case I am skipping bcrypt**



So What is happening in this resolver

- we are creating new resolver `userLogin`
- Return type is string (jwt)
- takes two arguments `email` and `password`
- we find user from database and if user not found then we will throw an error 
- if there is user then create a jwt token and return it 

let's see this in action 

First we need  a user so let's create one

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/sr3lx4gd0mp11pdqx8mw.png)

now Sign user in

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/plm5ge2hm3gm44azv8iq.png)

Great We have token Now we will send this token in authorization header so that our app can decode it and find the user by id

at the bottom of above image you see `Query Variables`  we can pass here http headers. Copy the token and pass it as following

```javascript
{
  "Authorization":"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySWQiOjEsImlhdCI6MTYwNTk3NDgyNX0.kYDgyvB3C0LYiam7ixUDzkQIbCAl19Gyp83MiR9q_S8"
}
```

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/kw9jwmz4kiy5yzum2vkl.png)

create a `utils/decocdejwt.ts` 

```javascript
import { verify } from "jsonwebtoken";

// export const APP_SECRET = process.env.APP_SECRET
export const APP_SECRET = "MyNewApp";

interface Token {
  UserId: string;   // <-- NOTE: capital "U"
}

export function getUserId(context) {
  const Authorization = context.req.headers["authorization"];
  if (Authorization) {
    const token = Authorization.replace("Bearer ", "");
    const verifiedToken = verify(token, APP_SECRET) as Token;
    const res = verifiedToken && verifiedToken.UserId;
    return res;
  }
}
```



**`getUserId` function will take context and from that it will get Authorization header and  decode the jwt and give us user's id** 

Now Let's go to `api/permissions/index.ts` and add new rule

```javascript
import { rule, shield } from "graphql-shield";
import { PrismaClient } from "@prisma/client";
import { getUserId } from "../../../utils/decodejwt";
const prisma = new PrismaClient();

const rules = {
  isAuthenticatedUser: rule()(async (__parent, _args, context) => {
    const userId = parseInt(getUserId(context));// it will return string but we need int here
    const User = await prisma.user.findOne({ where: { id: userId } });
    if (User) {
      return true;
    } else {
      return false;
    }
  }),
};

export const permissions = shield({
  Query: {},
  Mutation: {},
});

```

`isAuthenticatedUser` is our new rule that will return true if there is any user exists otherwise it will return false

Let's look at `createPost` mutation 

```javascript
 t.field("createDraft", {
      type: "Post",
      args: {
        title: stringArg({ nullable: false }),
        content: stringArg(),
        authorEmail: stringArg(),
      },
      resolve: (_, { title, content, authorEmail }, ctx) => {
        return prisma.post.create({
          data: {
            title,
            content,
            published: false,
            author: {
              connect: { email: authorEmail },
            },
          },
        });
      },
    });
```

- return type is **Post**
- takes three arguments `title`, `content` , `authorEmail`
- creates a new post and connect to user by provided email

Add this mutation to the permissions

```javascript
import { rule, shield } from "graphql-shield";
import { PrismaClient } from "@prisma/client";
import { getUserId } from "../../../utils/decodejwt";
const prisma = new PrismaClient();

const rules = {
  isAuthenticatedUser: rule()(async (__parent, _args, context) => {
    const userId = parseInt(getUserId(context));
    const User = await prisma.user.findOne({ where: { id: userId } });
    if (User) {
      return true;
    } else {
      return false;
    }
  }),
};

export const permissions = shield({
  Query: {},
  Mutation: {
    createDraft: rules.isAuthenticatedUser,  // <-- added
  },
});

```

Now Our `createDraft` should be protected. let's see this in action 

go to [localhost:3000/api](http://localhost:3000/api) open a new tab and remove `HTTP HEADERS` and try to create a new post. 

You should get an error `Not Authorised!`

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/4hw57ln6ic08hromwxia.png)

Let's Try this with `HTTP HEARDERS` (undo `ctrl + z`)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/0s7xb08uow071qhf4j8o.png)

All Mutation and Queries can be protect and additional rules can be applied. 

graphql-shield also provide several Other methods like `or`, `and`, `not` etc. which can be very helpful and flexible in creating different rules 

```javascript
import { rule, shield } from "graphql-shield";
import { PrismaClient } from "@prisma/client";
import { getUserId } from "../../../utils/decodejwt";
const prisma = new PrismaClient();

const rules = {
  isAuthenticatedUser: rule()(async (__parent, _args, context) => {
    const userId = parseInt(getUserId(context));
    const User = await prisma.user.findOne({ where: { id: userId } });
    if (User) {
      return true;
    } else {
      return false;
    }
  }),
};

export const permissions = shield({
  Query: {
    post: rules.isAuthenticatedUser,
    feed: rules.isAuthenticatedUser,
    drafts: rules.isAuthenticatedUser,
    filterPosts: rules.isAuthenticatedUser,
  },
  Mutation: {
    createDraft: rules.isAuthenticatedUser,
    deletePost: rules.isAuthenticatedUser,
    publish: rules.isAuthenticatedUser,
  },
});

```



**Note: if you are facing any trouble with above code you can checkout shield branch**

`git clone -b shield https://github.com/imran-ib/auntenticating-next-app.git`

Now when user logs in we will store the token in local storage and and send that token with every request. But first we need a query that will return a user if there is token. 

```javascript
 t.field('CurrentUser', {
      type: 'User',
      nullable: true,
      resolve: async (_root, _agrs, ctx) => {
          //get the user id from token
        const userId = parseInt(getUserId(ctx));
          
        if (!userId) return;
          
        return ctx.prisma.user.findOne({
          where: { id: userId }
        });
      },
    });
```

 

Now we can determine weather user is logged in or not. 

### Now Let's move on to client side

create a new page in `pages`  and name it private.

```javascript
import { withApollo } from "../apollo/client";

const PrivatePage = () => {
  return <h1>This is Private Page </h1>;
};
export default withApollo(PrivatePage);

```

User should not be able to visit this page if he/she is not logged in 

**NOTE: the post is already getting long so instead of creating login form we will send just email and password of user already registered directly in query **

let's modify `pages/index.ts` .

**Changes we will make in this component**

- import `gql` and `useMutation` from `@apollo/react-hooks`

- when user is successfully logged in we will push them to `private` route so for that we need router from `next/router`

- create a mutation  that will be responsible for logging user in

- use the Mutation 

- `useMutation` has a method called `onCompleted` that will be called on success of mutation. so we will grab the token when mutation is successfully executed and store the token to local storage and push the user to `private` route. 

- Handle the loading and error state

- create a button to log user in 

  

So `index.ts` should look like this now

```javascript
import { withApollo } from "../apollo/client";
import { gql, useMutation } from "@apollo/react-hooks";
import { useRouter } from "next/router";

const USER_LOGIN = gql`
  mutation UserLogin {
    userLogin(email: "john@exapmle.com", password: "123456")
  }
`;

const IndexPage = () => {
  const Router = useRouter();

  const [login, { data, loading, error }] = useMutation(USER_LOGIN, {
    onCompleted: (data) => {
      const token = data?.userLogin;
      localStorage.setItem("token", token);
      Router.push("/private");
    },
  });
  if (loading) return <p>Loading...</p>;
  if (loading) return <p>{error.message}</p>;
  return (
    <>
      <h1>Hello Next.js 👋</h1>
      <button onClick={() => login()}>Login</button>
    </>
  );
};
export default withApollo(IndexPage);

```



Now go to `localhost:3000` and open developer tools and go to application tab. on the left side open local storage panel 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/9er4dmlyrd9xsq9eeigs.png)

Let's try this

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/6lem4la6fuz8n6pb88a1.png)

**GREATE**

Now We will get that token from local storage and send it with every request 

got  `apollo/client.js` and update `createApolloClient`

we need to change the way we are creating client. we will create two links `authLink` and `httpLink` and concat the togehter

```javascript
function createApolloClient() {
  // Declare variable to store authToken
  let token;

  const httpLink = createHttpLink({
    uri: "http://localhost:3000/api",
    credentials: "include",
  });

  const authLink = setContext((_, { headers }) => {
    // get the authentication token from local storage if it exists
    if (typeof window !== "undefined") {
      token = localStorage.getItem("token");
    }
    // return the headers to the context so httpLink can read them
    return {
      headers: {
        ...headers,
        Authorization: token ? `Bearer ${token}` : "",
      },
    };
  });

  const client = new ApolloClient({
    ssrMode: typeof window === "undefined",
    link: authLink.concat(httpLink),
    cache: new InMemoryCache(),
  });

  return client;
}
```



**Entire File**

```javascript
import React from "react";
import Head from "next/head";
import { ApolloProvider, createHttpLink } from "@apollo/react-hooks";
import { ApolloClient } from "apollo-client";
import { InMemoryCache } from "apollo-cache-inmemory";
import { setContext } from "@apollo/client/link/context";

let apolloClient = null;

/**
 * Creates and provides the apolloContext
 * to a next.js PageTree. Use it by wrapping
 * your PageComponent via HOC pattern.
 * @param {Function|Class} PageComponent
 * @param {Object} [config]
 * @param {Boolean} [config.ssr=true]
 */
export function withApollo(PageComponent, { ssr = true } = {}) {
  const WithApollo = ({ apolloClient, apolloState, ...pageProps }) => {
    const client = apolloClient || initApolloClient(apolloState);
    return (
      <ApolloProvider client={client}>
        <PageComponent {...pageProps} />
      </ApolloProvider>
    );
  };

  // Set the correct displayName in development
  if (process.env.NODE_ENV !== "production") {
    const displayName =
      PageComponent.displayName || PageComponent.name || "Component";

    if (displayName === "App") {
      console.warn("This withApollo HOC only works with PageComponents.");
    }

    WithApollo.displayName = `withApollo(${displayName})`;
  }

  if (ssr || PageComponent.getInitialProps) {
    WithApollo.getInitialProps = async (ctx) => {
      const { AppTree } = ctx;

      // Initialize ApolloClient, add it to the ctx object so
      // we can use it in `PageComponent.getInitialProp`.
      const apolloClient = (ctx.apolloClient = initApolloClient());

      // Run wrapped getInitialProps methods
      let pageProps = {};
      if (PageComponent.getInitialProps) {
        pageProps = await PageComponent.getInitialProps(ctx);
      }

      // Only on the server:
      if (typeof window === "undefined") {
        // When redirecting, the response is finished.
        // No point in continuing to render
        if (ctx.res && ctx.res.finished) {
          return pageProps;
        }

        // Only if ssr is enabled
        if (ssr) {
          try {
            // Run all GraphQL queries
            const { getDataFromTree } = await import("@apollo/react-ssr");
            await getDataFromTree(
              <AppTree
                pageProps={{
                  ...pageProps,
                  apolloClient,
                }}
              />
            );
          } catch (error) {
            // Prevent Apollo Client GraphQL errors from crashing SSR.
            // Handle them in components via the data.error prop:
            // https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error
            console.error("Error while running `getDataFromTree`", error);
          }

          // getDataFromTree does not call componentWillUnmount
          // head side effect therefore need to be cleared manually
          Head.rewind();
        }
      }

      // Extract query data from the Apollo store
      const apolloState = apolloClient.cache.extract();
      return {
        ...pageProps,
        apolloState,
      };
    };
  }

  return WithApollo;
}

/**
 * Always creates a new apollo client on the server
 * Creates or reuses apollo client in the browser.
 * @param  {Object} initialState
 */
function initApolloClient(initialState) {
  // Make sure to create a new client for every server-side request so that data
  // isn't shared between connections (which would be bad)
  if (typeof window === "undefined") {
    return createApolloClient(initialState);
  }

  // Reuse client on the client-side
  if (!apolloClient) {
    apolloClient = createApolloClient(initialState);
  }

  return apolloClient;
}

/**
 * Creates and configures the ApolloClient
 * @param  {Object} [initialState={}]
 */

function createApolloClient() {
  // Declare variable to store authToken
  let token;

  const httpLink = createHttpLink({
    uri: "http://localhost:3000/api",
    credentials: "include",
  });

  const authLink = setContext((_, { headers }) => {
    // get the authentication token from local storage if it exists
    if (typeof window !== "undefined") {
      token = localStorage.getItem("token");
    }
    // return the headers to the context so httpLink can read them
    return {
      headers: {
        ...headers,
        Authorization: token ? `Bearer ${token}` : "",
      },
    };
  });

  const client = new ApolloClient({
    ssrMode: typeof window === "undefined",
    link: authLink.concat(httpLink),
    cache: new InMemoryCache(),
  });

  return client;
}

```

Now we are sending auth token with every request. 

Now Go To `pages/private.tsx` and create new query that will fetch current user

```javascript
const CurrentUser = gql`
  query CurrentUser {
    CurrentUser {
      id
      name
      email
    }
  }
`;
```

get the data and handle loading and error state 

```javascript
  //NOTE we should use router in client side component instead of pages
  const Router = useRouter();
  const { data, loading, error } = useQuery(CurrentUser);
  if (loading) return <p>Loading...</p>;
  if (loading) return <p>{error.message}</p>;
  const user = data.CurrentUser;
  if (!user) Router.back();

```

if there is current user we will let them through otherwise we will push them back to home page

```javascript
import { gql, useQuery } from "@apollo/react-hooks";
import { withApollo } from "../apollo/client";
import { useRouter } from "next/router";

const CurrentUser = gql`
  query CurrentUser {
    CurrentUser {
      id
      name
      email
    }
  }
`;

const PrivatePage = () => {
  //NOTE we should use router in client side component instead of pages
  const Router = useRouter();
  const { data, loading, error } = useQuery(CurrentUser);
  if (loading) return <p>Loading...</p>;
  if (loading) return <p>{error.message}</p>;
  const user = data.CurrentUser;
  if (!user) Router.back();

  return (
    <>
      <h1>This is Private Page </h1>
      {user?.id}
      {user?.email}
      {user?.name}
    </>
  );
};
export default withApollo(PrivatePage);

```

you can have a hook `useUser` where you can check weather there is a user logged in or not. import that hook in every component where needed.