The time and comfort of the modern man cannot be negotiated. Gone are the days where users go through a long sequence of input fields in order to sign up into a software. This however brought about the innovation of a passwordless onboarding system where time and energy are conserved.
In this article I would work you through a flow process on how to setup user authentication just in two clicks using Next.js and it's authentication library NextAuth.js .
What is Next.js?
Next.js is a framework built on top of React that makes developing production-ready, fully-optimized React apps super fast and easy. One of the main characteristics of Next.js is its run time speed.
Next.js is used in production by top companies like Netflix, Tiktok, and Nike. It is super easy to learn, especially if you’re familiar with React.
What is NextAuth.js?
NextAuth.js is a Next.js authentication library. It exists as an abstraction of the OAuth library as it leverages on its authentication and authorization providers (such as Google, Twitter, Facebook, GitHub). With NextAuth.js a software can have access to a user's profile information on adequate permission by the user.
NextAuth.js has a client-side API you can use to interact with sessions in your app. The session data returned from the Providers contains user payload, and this can be displayed to the user upon successful login.
The session data returned to the client looks like this:
{
expires: '2055-12-07T09:56:01.450Z';
user: {
email: 'newsuer@example.com';
image: 'https://avatars2.githubusercontent.com/u/45228014?v=4';
name: 'newuser';
}
}
Requirements
- Node.js 10.13 or later installed on your local machine
- Basics of React.js
Create a new Next.js project
Creating a Next.js app is similar to creating a new react app. At first we created a new directory in our case my_projects in our new created directory we create a new instance of a next app into a the next_auth directory.
mkdir my_projects
cd my_projects
npx create-next-app@latest next_auth
NB: @latest ensures that our installation comes along with the latest Next.js version.
Once this is done an instance of a Next app would be available in our project directory.
Installing Packages
Our next line of action would be to install the necessary packages.
npm i next-auth axios jsonwebtoken --save
or
yarn add next-auth axios jsonwebtoken --save
next-auth:- Next.js authentication library the backbone of our authentication build.
jsonwebtoken:- used in signing a user payload which could be a user Id or email in order to generate a token necessary for authorization.
axios:- with axios we would be making two (2) API calls to our server. The first is to confirm if the authenticating email already exists if yes then it logs in the user, else it calls the second API which is responsible for storing user details in the database. We would look into this more as we proceed.
Upon successful installation, next-auth should be added to the dependencies in your package.json file:
//Dependencies in package.json
"dependencies": {
"dependencies": {
"axios": "^0.27.2",
"jsonwebtoken": "^8.5.1",
"next": "latest",
"next-auth": "^4.10.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
}
}
Create a Google OAuth app
To allow users to log in to our app using their Google account, we have to obtain OAuth 2.0 client credentials from the Google API Console. Navigate to Credentials and click on Create credentials, and then OAuth client ID:
You would be asked to fill in the following:
- Choose an Application Type: Select Web Application
- Name: This is the name of your application
- Authorized JavaScript origins: This is the full URL to the homepage of our app. Since we are still in development mode, we are going to fill in the full URL our development server is running on. In this case, it is http://localhost:3000.
NB: Remember to change your home page URL once your app gets into production mode.
- Authorized redirect URIs: Users will be redirected to this path after they have authenticated successfully with Google: http://localhost:3000/api/auth/callback/google
NB: These URLs are custom URLs none of these them are assigned to our software by google.
Next, a popup will display your client ID and client secret. Copy both keys to your clip board.
Add an environmental variable
Next, create an .env.local file in your project’s root directory. Next.js has inbuilt support for environment variables and .env.local will load those variables to process.env. Therefore, the filename cannot be a random name. For more information, don’t hesitate to read Next.js documentation on environment variables.
Next, populate the newly created file with my OAuth credentials we had copied earlier.
GOOGLE_ID=<client id of your google auth app should go in here>
GOOGLE_SECRET=<client secret of your google auth app should go in here>
NEXTAUTH_URL= http://localhost:3000
NEXT_PUBLIC_JWT_SECRET_KEY: <your jwt secret key>
Google Provider Initialization
Now, back to the app. I created a file named [...nextauth].js in pages/api/auth.
In the file add the following code:
import NextAuth from 'next-auth'
import GoogleProvider from "next-auth/providers/google";
const options = {
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET
}),
],
}
On line 1, I imported NextAuth, which is main package behind the build.
On line 2,I imported Google Provider from the next-auth library,
On line 6, I configured the Google provider and passing in my Google secret and client ID through the environmental variables.
Setup action page
NextAuth provides an option to setup custom pages which would be returned to the browser depending on several line of action tolled during the course of our user authentication.
The pages object is placed directly below the providers array.
const options = {
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET
}),
],
pages: {
signIn: '/auth/dashbaord', // on successfully signin
signOut: '/auth/login', // on signout redirects users to a custom login page.
error: '/auth/error', // displays authentication errors
newUser: '/auth/new-user' // New users will be directed here on first sign in (leave the property out if not of interest)
}
}
Authentication Callbacks
Callbacks are asynchronous functions used to control what happens when an action is performed. Request to the backend API is handled using callbacks.
NextAuth provides us with certain vital callbacks which include:
- Sign in callback : Use the signIn() callback to control if a user is allowed to sign in. With in this object we would be making two axios request to our backend API, the first one would be to verify if our user exists, this request would query the database to verify if the user's email exists if yes it returns true signifying that the user want's to login to his account, if the query returns false we would then make our second axios request which receives the user profile object returned from google and stores it in our custom database.
lets see how this really works:-
callbacks: {
async signIn({ user, account, profile, email, credentials }) {
// first axios request to ascertain if our user exists in our custom DB
const response = await axios.post(
"http://localhost:9000/v1/auth/userExists",
{ email: profile.email }
);
if (response && response.data?.value === true) {
// user exists return true passing control to the next callback
return true;
} else {
// second axios call which creates a new user in our database
const data = {
firstName: profile.given_name,
lastName: profile.family_name,
email: profile.email,
profileUrl: profile.picture,
};
const response = await axios.post(
"http://localhost:9000/v1/auth/signup",
data
);
// retruns true thereby passing control to the next callback
return true;
}
},
}
- JWT callback : this callback takes in a user object, token object as it's parameters. The user object contains the details of the signed in user, on line 5, SignToken() function is called which returns a jwt token, this token is appended to our callback token object as userToken,
token.userToken = token;
async jwt({ token, user, account }) {
if (account) {
console.log(user, token, account);
// call the signToken function which returns a JWT token
const token = await SignToken(user?.email as string);
token.userToken = token;
}
// the token object is passed done to the session call back for persistence
return token;
},
our SignToken() function:
import jwt from 'jsonwebtoken';
const SignToken = async (email)=> {
const token = await jwt.sign({id:email}, process.env.NEXT_PUBLIC_JWT_SECRET_KEY, {expiresIn: '1d'});
return token
}
export default SignToken;
- Session Callback: Session callback takes in the token object passed in from the jwt callback seen above, and a session object. The jwt token is appended to the session object as loggedUser;
session.loggedUser = token.userToken;
The session object get persisted in our browser session for authorization at any point. The session callback is called whenever a session is checked.
async session({ session, token, user }) {
// Send properties to the client, like an access_token from a provider.
session.loggedUser = token.userToken;
return session;
},
},
A glimpse of how the browser's session object now looks like;
{
expires: '2055-12-07T09:56:01.450Z';
// our jwt token
loggedUser: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCI1NiIsInR"
user: {
email: 'newsuer@example.com';
image: 'https://avatars2.githubusercontent.com/u/45228014?v=4';
name: 'newuser';
}
}
Put together our [...nextauth].js file looks like:
import GoogleProvider from "next-auth/providers/google";
import NextAuth from "next-auth";
import axios from "axios";
import { SignToken } from "../../../utils/siginToken";
export default NextAuth({
providers: [
GoogleProvider({
clientId: <string>process.env.GOOGLE_CLIENT_ID,
clientSecret: <string>process.env.GOOGLE_CLIENT_SECRET,
}),
],
pages: {
signIn: '/auth/dashbaord',
signOut: '/auth/login',
error: '/auth/error',
newUser: '/auth/new-user'
}
callbacks: {
async signIn({ user, account, profile}) {
const response = await axios.post(
"http://localhost:9000/v1/auth/userExists",
{ email: profile.email }
);
if (response && response.data?.value === true) {
return true;
} else {
const data = {
firstName: profile.given_name,
lastName: profile.family_name,
email: profile.email,
profileUrl: profile.picture,
};
const response = await axios.post(
"http://localhost:9000/v1/auth/signup",
data
);
return true;
}
},
async jwt({ token, user, account }) {
if (account) {
const userLoggedIn = await SignToken(user?.email as string);
token.loggedUser = userLoggedIn;
}
return token;
},
async session({ session, token, user }) {
session.loggedUser = token.loggedUser;
return session;
},
},
});
Check user login state and jwt token with the useSession() Hook
Create an _app.js file in your pages directory (if it doesn’t already exist) and add the following code:
import { SessionProvider } from "next-auth/react"
import '../styles/globals.css'
function MyApp({ Component, pageProps }) {
return (
<SessionProvider session={pageProps.session}>
<Component {...pageProps} />
</SessionProvider>
)
}
export default MyApp
By wrapping the component in a Session Provider, I enabled session state to be shared between pages. This, in turn, will preserve the state during page navigation, improve performance, and reduce network traffic.
User Sign In / Sign Out Buttons
Next, open the components/Header.js file and import useSession, signIn, and signOut from next-auth/client:
import { useSession, signIn, signOut } from 'next-auth/react'
useSession will be used to manage the sign in and sign out state of our users, while signIn and signOut will be used to perform the login and logout features in our app.
Let’s make use of the useSession Hook:
const { data: session } = useSession();
The session will return the user’s details. Let’s use the details returned to conditionally render a sign in and sign out button.
With in the body of our Header.js file we would append the signin / signout conditional logic:
<div className='header'>
<Link href='/'>
<a className='logo'>NextAuth.js</a>
</Link>
{session && <a href="#" onClick={handleSignout} className="btn-signin">Sign out</a> }
{!session && <a href="#" onClick={handleSignin} className="btn-signin">Sign in</a> }
</div>
We need to create the handleSignin and handleSignout methods to enable our users to sign in and sign out:
const handleSignin = (e) => {
e.preventDefault()
signIn()
}
const handleSignout = (e) => {
e.preventDefault()
signOut()
}
My browser display once after the click of the signin button:
Your Header.js should now look like this:
import Link from 'next/link'
import { useSession, signIn, signOut } from 'next-auth/react'
export default function Header () {
const { data: session } = useSession();
const handleSignin = (e) => {
e.preventDefault()
signIn()
}
const handleSignout = (e) => {
e.preventDefault()
signOut()
}
return (
<div className='header'>
<Link href='/'>
<a className='logo'>NextAuth.js</a>
</Link>
{session && <a href="#" onClick={handleSignout} className="btn-signin">Sign out</a> }
{!session && <a href="#" onClick={handleSignin} className="btn-signin">Sign in</a> }
</div>
)
}
Retrieve and display user information
Now, onto our pages/index.js. I need to display and conditionally render user details based on their login details. First, I imported the useSession hook from next-auth.
So, replace your index.js with the following content:
import Head from 'next/head'
import Header from '../components/Header'
import styles from '../styles/Home.module.css'
import { useSession } from 'next-auth/react'
export default function Home() {
const { data: session, status } = useSession()
const loading = status === "loading"
return (
<div className={styles.container}>
<Head>
<title>Nextjs | Next-Auth</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Header />
<main className={styles.main}>
<h1 className={styles.title}>Authentication in Next.js app using Next-Auth</h1>
<div className={styles.user}>
{loading && <div className={styles.title}>Loading...</div>}
{
session &&
<>
<p style={{ marginBottom: '10px' }}> Welcome, {session.user.name ?? session.user.email}</p> <br />
<img src={session.user.image} alt="" className={styles.avatar} />
</>
}
{
!session &&
<>
<p className={styles.title}>Please Sign in</p>
</>
}
</div>
</main>
</div>
)
}
NB: In order gain access to some backend authorized API's you can now access your jwt token appended in user session. Which can now be appended to the header of an authorized API call.
import { useSession } from 'next-auth/react
export default function JWT() {
const { data: session, status } = useSession()
// jwt_token
const jwt_token = session.loggedUser;
return {token}
}
in the above hook I created a react hook used to return an authenticated user's token.
Conclusion
Through this article we have been able to understand the development flow process of google authentication using NextAuth library. We further explained how we could interact with a custom backend API using axios as well as JSON web token for authorizing users.
if you did like to know more about Next.js kindly checkout their documentation at https://nextjs.org/docs
more details on NextAuth.js, how to integrate other authentication Strategies like GitHub, Twitter and others can be seen in their documentation at: https://next-auth.js.org/getting-started/introduction
Top comments (1)
Git Hub url?