Get started with Next-Auth and EasyAuth
Learn how to authenticate users through NextAuth using EasyAuth provider.
TLDR: Try the sample Easyauth NextAuth project
-
Sign in to EasyAuth and create a new 'Registered Client' with redirect URI set to
http://127.0.0.1:3000/auth/easyauth/callback
. -
Clone the sample app.
git clone https://github.com/easyauth-io/easyauth-next-auth-example.git
-
Copy
.env
into.env.local
cp .env .env.local
-
Edit
.env.local
and set the parameters from your 'Registered Client' that you created in Step 1. -
Run
npm install
followed bynpm run dev
-
Visit http://127.0.0.1:3000
Note: Replace the port 3000
in .env.local
and redirect URI with the corresponding port where your app is runing.
1. Create a new Next.js Application
The following command creates a Next.js app. Configure the prompts as required.
2. Install @easyauth.io/easyauth-next-auth & next-auth
3. Set EasyAuth keys in .env.local
file
Create a .env.local
file. Fill in the values by logging in to your EasyAuth dashboard.
The NEXTAUTH_URL
will be the url where your app will be running. In this case, for development purpose it will be running on http://127.0.0.1:3000
.
NEXTAUTH_URL=http://127.0.0.1:3000
EASYAUTH_CLIENT_ID=<client_id>
EASYAUTH_CLIENT_SECRET=<client_secret>
NEXT_PUBLIC_EASYAUTH_TENANT_URL=<tenant_url>
4. Create [...nextauth].js
file & setup next-auth
Refer the next-auth documentation and create [...nextauth].js
file in pages/api/auth
directory. Setup next-auth as follows.
import NextAuth from "next-auth";
import EasyAuth from "@easyauth.io/easyauth-next-auth";
export const authOptions = {
providers: [
EasyAuth({
clientId: process.env.EASYAUTH_CLIENT_ID,
clientSecret: process.env.EASYAUTH_CLIENT_SECRET,
tenantURL: process.env.NEXT_PUBLIC_EASYAUTH_TENANT_URL,
}),
],
secret: "my-secret",
callbacks: {
async jwt({token, account, profile}) {
//Refresh token implementation
if (account) {
return {
access_token: account.access_token,
expires_at: account.expires_at,
refresh_token: account.refresh_token,
...token,
};
} else if (Date.now() < token.expires_at * 1000) {
return token;
} else {
try {
const response = await fetch(
new URL(
"/tenantbackend/oauth2/token",
process.env.NEXT_PUBLIC_EASYAUTH_TENANT_URL
),
{
headers: {"Content-Type": "application/x-www-form-urlencoded"},
body: new URLSearchParams({
client_id: process.env.EASYAUTH_CLIENT_ID,
client_secret: process.env.EASYAUTH_CLIENT_SECRET,
grant_type: "refresh_token",
refresh_token: token.refresh_token,
}),
method: "POST",
}
);
const tokens = await response.json();
if (!response.ok) throw tokens;
return {
...token, // Keep the previous token properties
access_token: tokens.access_token,
expires_at: Math.floor(Date.now() / 1000 + tokens.expires_in),
refresh_token: tokens.refresh_token ?? token.refresh_token,
};
} catch (error) {
console.error("Error refreshing access token", error);
return {...token, error: "RefreshAccessTokenError"};
}
}
},
async session({session, token}) {
session.user.access_token = token.access_token;
session.error = token.error;
return session;
},
},
};
export default NextAuth(authOptions);
5. Create util/checkRefreshTokenError.js
file
This is to resolve error in refreshing Access token occured at jwt callback above. The error is generally due to expired Refresh token.
import {signOut, useSession} from "next-auth/react";
import {useEffect} from "react";
export function checkRefreshTokenError() {
const {data: session} = useSession();
useEffect(() => {
if (session?.error === "RefreshAccessTokenError") {
// Force sign out to resolve error
// Generally the error is due to expired Refresh token.
signOut({redirect: false}).then(() => {
// To signout from EasyAuth Tenant.
window.location.assign(
`${process.env.NEXT_PUBLIC_EASYAUTH_TENANT_URL}/logout?target=${btoa(
window.location.href
)}`
);
});
}
}, [session]);
}
6. Edit pages/index.js
file
import Head from "next/head";
import {useSession, signIn, signOut} from "next-auth/react";
import {checkRefreshTokenError} from "@/util/checkRefreshTokenError";
import Link from "next/link";
export default function Home() {
const {data: session, status} = useSession();
checkRefreshTokenError();
return (
<>
<Head>
<title>EasyAuth NextAuth example</title>
<meta name="description" content="Generated by create next app" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</Head>
<main>
<div className="hero">
{status !== "loading" && (
<>
{session ? (
<>
Signed in as {session.user.email} <br />
<button
onClick={() => {
signOut({redirect: false}).then(() => {
// To signout from EasyAuth Tenant.
window.location.assign(
`${
process.env.NEXT_PUBLIC_EASYAUTH_TENANT_URL
}/logout?target=${btoa(window.location.href)}`
);
});
}}
>
Sign out
</button>
<Link href={"/profile"}>
<button>EasyAuth user profile</button>
</Link>
</>
) : (
<>
Not signed in . <br />
<button onClick={() => signIn("easyauth")}>Sign in</button>
</>
)}
<Link href={"/protected"}>
<button>Protected page</button>
</Link>
</>
)}
</div>
</main>
</>
);
}
7. Create pages/protected.js
file
This page is protected from unauthenticated users.
import {useSession} from "next-auth/react";
import {useRouter} from "next/router";
import {checkRefreshTokenError} from "@/util/checkRefreshTokenError";
import Link from "next/link";
export default function ProtectedPage() {
const router = useRouter();
const {data: session, status} = useSession({
required: true,
onUnauthenticated() {
router.push("/");
},
});
checkRefreshTokenError();
if (status === "loading") {
return null;
}
return (
<div className="hero">
<h1>Protected Page</h1>
<p>Welcome, {JSON.stringify(session.user.email)}</p>
<Link href={"/"}>
<button>Home page</button>
</Link>
</div>
);
}
8. Create pages/profile.js
file
Page for fetching EasyAuth profile details.
import {useSession} from "next-auth/react";
import {useRouter} from "next/router";
import React, {useEffect, useState} from "react";
import {checkRefreshTokenError} from "@/util/checkRefreshTokenError";
import Link from "next/link";
export default function Profile() {
const router = useRouter();
const {data: session, status} = useSession({
required: true,
onUnauthenticated() {
router.push("/");
},
});
const [easyAuthProfile, setEasyAuthProfile] = useState(null);
checkRefreshTokenError();
useEffect(() => {
if (session) {
fetch(
new URL(
"/tenantbackend/api/profile",
process.env.NEXT_PUBLIC_EASYAUTH_TENANT_URL
),
{
method: "GET",
headers: {
Authorization: `Bearer ${session.user.access_token}`,
},
}
)
.then((res) => res.json())
.then((data) => setEasyAuthProfile(data))
.catch((err) => console.error("Failed to fetch EasyAuth profile"));
}
}, [status]);
if (status === "loading") {
return null;
}
return (
<div className="hero">
<h3>EasyAuth profile</h3>
<p>{easyAuthProfile && JSON.stringify(easyAuthProfile)}</p>
<Link href={"/"}>
<button>Home page</button>
</Link>
</div>
);
}
9. Add some CSS
.hero {
display: flex;
flex-direction: column;
align-items: center;
padding: 5em 0;
gap: 2rem;
font-size: 1.3rem;
}
.hero button {
cursor: pointer;
padding: 10px 20px;
border: none;
background-color: rgb(34, 139, 230);
color: white;
}
.hero button:hover {
background-color: rgb(21, 110, 188);
}
Test the App
Run the application and then open the URL http://127.0.0.1:3000.