Setting up authentication on a react project with typescript is a difficult task. With the new Nextjs app router version, it is much more difficult to manage storage and cookies because it has gotten more complex and server-centric.
I'm here to share some code snippets for basic authentication and user management in Nextjs (>13) + typescript.
Prerequisite
- Basic understanding of javascript and typescript
- Basic knowledge of Nextjs/React
Steps involved
- Setting up a nextjs project
- Adding basic login page
- Access and modify cookie using custom hook
- Creating AuthContext.tsxto persist user
- Including custom hooks for managing and modifying user
- Implementation inside components
 
Setting up a nextjs project
We will be using the app router version of nextjs, which was recently released. Have a look on this page. It includes neccessary details and some FAQs.
- Enter this command and answer the promt question to setup next project:
npx create-next-app@latest
Note: While answering, try to use recommended/default options.
Check this page for customized manual installations.
- Install required packages
cd next13-auth  # Use your project-name instead
rm package-lock.json
yarn
yarn add axios formik next-client-cookies
I prefer using yarn over npm cli. However, you don't have to remove
package.jsonif you are using npm commands.
- Start server by running
yarn dev
- Clean up the code that was generated by nextjs
- Our final project structure looks like this:
Note: Refer docs for better undertanding of recommended nextjs project structure.
Adding basic login page
- Add required type declaration for authentication
// /utils/types/auth.d.ts
export type TUser = {
  email: string;
  firstName: string;
  lastName: string;
};
export type AuthUser = {
  token: string;
  user: TUser;
};
export type TLogin = {
  email: string;
  password: string;
};
export type AuthResponse = {
  message: string;
  data?: AuthUser;
  success?: boolean;
};
Note: You might have to add/remove and structure types according to your needs and response format from backend!
- Add login page to your app directory.
// /app/login/page.tsx
"use client";
import { TLogin } from "@/utils/types/auth";
import { Field, Form, Formik, FormikHelpers } from "formik";
import React from "react";
const Login = () => {
  const handleSubmit = (values: TLogin) => {
    console.log(values);
  };
  return (
    <div className="max-w-[100vw] p-5">
      <h3 className="mb-5 text-4xl font-medium">Login</h3>
      <Formik
        initialValues={{
          email: "",
          password: "",
        }}
        onSubmit={(
          values: TLogin,
          { setSubmitting }: FormikHelpers<TLogin>
        ) => {
          setTimeout(() => {
            handleSubmit(values);
            setSubmitting(false);
          }, 500);
        }}
      >
        <Form className="grid w-96 grid-cols-2 gap-3">
          <label htmlFor="email">Email</label>
          <Field
            id="email"
            name="email"
            placeholder="Enter email"
            type="email"
          />
          <label htmlFor="password">Password</label>
          <Field
            id="password"
            name="password"
            type="password"
            placeholder="Enter password"
          />
          <button
            type="submit"
            className="w-fit border border-black/75 px-4 py-1"
          >
            Submit
          </button>
        </Form>
      </Formik>
    </div>
  );
};
export default Login;
Note: I will only walk you through creating a login page; you should create/use a backend that can handle basic authentication requests. Also, populate your database with a user to test login action.
Access and modify cookie using custom hook
- Create a custom hook useCookie.tsto modify cookie values
// /hooks/useCookie.ts
import { useCookies } from "next-client-cookies";
const useCookie = () => {
  const cookies = useCookies();
  const getCookie = (key: string) => cookies.get(key);
  const setCookie = (key: string, value: string) =>
    cookies.set(key, value, {
      expires: 2,
      sameSite: "None",
      secure: true,
    });
  const removeCookie = (key: string) => cookies.remove(key);
  return { setCookie, getCookie, removeCookie };
};
export default useCookie;
- Create cookies.tsxandproviders.tsxinsideappdirectory
// /app/cookies.tsx
"use client";
import { CookiesProvider } from "next-client-cookies";
export const ClientCookiesProvider: typeof CookiesProvider = (props) => (
  <CookiesProvider {...props} />
);
// /app/providers.tsx
import { ClientCookiesProvider } from "./cookies";
import { cookies } from "next/headers";
export function Providers({ children }: React.PropsWithChildren) {
  return (
    <ClientCookiesProvider value={cookies().getAll()}>
      {children}
    </ClientCookiesProvider>
  );
}
- Add provider.tsxas a wrapper inside layout
// /app/layout.tsx
<body className={`${outfit.className} relative min-h-screen w-screen`}>
  <Providers>
    <main className="w-screen">{children}</main>
  </Providers>
</body>
Refer this readme for detailed explaination.
  
  
  Creating AuthContext.tsx to persist user
- Create AuthContext.tsx
// /contexts/AuthContext.tsx
"use client";
import { ReactNode, createContext, useEffect, useState } from "react";
import { AuthUser } from "@/utils/types/auth";
import useCookie from "@/hooks/useCookie";
interface TAuthContext {
  user: AuthUser | null;
  setUser: (user: AuthUser | null) => void;
}
export const AuthContext = createContext<TAuthContext>({
  user: null,
  setUser: () => {},
});
interface Props {
  children: ReactNode;
}
export const AuthProvider = ({ children }: Props) => {
  const [user, setUser] = useState<AuthUser | null>(null);
  const { getCookie } = useCookie();
  useEffect(() => {
    if (!user) {
      let existingUser = null;
      const getFromCookie = async () => (existingUser = getCookie("user"));
      getFromCookie();
      if (existingUser) {
        try {
          setUser(JSON.parse(existingUser));
        } catch (e) {
          console.log(e);
        }
      }
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);
  return (
    <AuthContext.Provider value={{ user, setUser }}>
      {children}
    </AuthContext.Provider>
  );
};
- Add AuthProviderinproviders.tsx
// /app/provider.tsx
<ClientCookiesProvider value={cookies().getAll()}>
  <AuthProvider>{children}</AuthProvider>
</ClientCookiesProvider>
Including custom hooks for managing and modifying user
This custom hooks are basically an extension that makes accessing and modifying user in authentication and other components easier without using cookies methods directly.
- Add useAuth.tsto manage auth API requests and their response
// /hooks/useAuth.ts
import { useUser } from "./useUser";
import config from "@/utils/config";
import axios from "axios";
import { AuthResponse, TLogin, TRegister } from "@/utils/types/auth";
import useCookie from "./useCookie";
const API_URL = config.BACKEND_URL;
export const useAuth = () => {
  const { user, addUser, removeUser } = useUser();
  const { getCookie } = useCookie();
  const refresh = () => {
    let existingUser = null;
    const getFromCookie = async () => (existingUser = getCookie("user"));
    getFromCookie();
    if (existingUser) {
      try {
        addUser(JSON.parse(existingUser));
      } catch (e) {
        console.log(e);
      }
    }
  };
  const register = async (creds: TRegister) => {
    return await axios
      .post(`${API_URL}auth/register`, creds)
      .then((res) => {
        if (res.data?.data && res.data.data?.token) addUser(res.data.data);
        return res.data as AuthResponse;
      })
      .catch((err) => {
        if (err && err?.response && err.response?.data)
          return { ...err.response.data, success: false } as AuthResponse;
        else return err as AuthResponse;
      });
  };
  const login = async (creds: TLogin) => {
    return await axios
      .post(`${API_URL}auth/login`, creds)
      .then((res) => {
        if (res.data?.data && res.data.data?.token) addUser(res.data.data);
        return res.data as AuthResponse;
      })
      .catch((err) => {
        if (err && err?.response && err.response?.data)
          return { ...err.response.data, success: false } as AuthResponse;
        else return err as AuthResponse;
      });
  };
  const logout = () => {
    removeUser();
  };
  return { user, login, register, logout, refresh };
};
Update authentication requests and response handling according to you backend.
- Add useUser.tsto export functions to add/remove user
// /hooks/useUser.ts
import { useContext } from "react";
import { AuthContext } from "../contexts/AuthContext";
import { AuthUser } from "@/utils/types/auth";
import useCookie from "./useCookie";
export const useUser = () => {
  const { user, setUser } = useContext(AuthContext);
  const { setCookie, removeCookie } = useCookie();
  const addUser = (user: AuthUser) => {    
    setUser(user);
    setCookie("user", JSON.stringify(user));
  };
  const removeUser = () => {
    setUser(null);
    removeCookie("user");
  };
  return { user, addUser, removeUser };
};
Implementation inside components
- Update your login submit handler function with login auth function
// /app/login/page.tsx
const handleSubmit = (values: TLogin) => {
  console.log(values);
  login(values)
    .then((data) => {
      if (data?.success) {
        // add your code for post successful login here
        setTimeout(() => {
          router.push("/");
        }, 1000);
      } else console.log(data.message);
    })
    .catch((err) => {
      console.log(err);
    });
};
- Finally, use this value across your components
example:
// /app/page.tsx
"use client";
import { useUser } from "@/hooks/useUser";
import Link from "next/link";
export default function Home() {
  const { user } = useUser();
  return (
    <div className="p-5">
      <h2 className="text-3xl font-medium">Hello Nextjs</h2>
      <p className="my-5 text-sm font-mono">
        Cookie-user: <pre>{JSON.stringify(user, undefined, 4)}</pre>
      </p>
      <Link
        href={"/login"}
        className="py-1 px-4 border border-black/75"
      >
        Login
      </Link>
    </div>
  );
}
Source code: nextjs-auth
Conclusion
I tried to make it as broad as possible. Changes and upgrades should be made based on your use cases.
I couldn't find any helpful blogs on this topic, so I'm hoping this can help someone who is looking for something similar. Please feel free to make any recommendations. Thank you for your time!
 
 
              


 
    
Top comments (4)
Can you share the github source code?
Sure, nextjs-auth.
Bravo ! nice Article
❤️