This commit is contained in:
Kar
2023-05-20 19:16:59 +05:30
parent 37786c459e
commit 54f74f8860
10 changed files with 547 additions and 10 deletions

View File

@@ -0,0 +1,18 @@
import { useSession } from "next-auth/react";
import { useEffect } from "react";
const RefreshTokenHandler = (props) => {
const { data: session } = useSession();
useEffect(() => {
if(!!session) {
// We did set the token to be ready to refresh after 23 hours, here we set interval of 23 hours 30 minutes.
const timeRemaining = Math.round((((session.accessTokenExpiry - 30 * 60 * 1000) - Date.now()) / 1000));
props.setInterval(timeRemaining > 0 ? timeRemaining : 0);
}
}, [session]);
return null;
}
export default RefreshTokenHandler;

29
src/components/useAuth.js Normal file
View File

@@ -0,0 +1,29 @@
import { signOut, useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
export default function useAuth(shouldRedirect) {
const { data: session } = useSession();
const router = useRouter();
const [isAuthenticated, setIsAuthenticated] = useState(false);
useEffect(() => {
if (session?.error === "RefreshAccessTokenError") {
signOut({ callbackUrl: '/login', redirect: shouldRedirect });
}
if (session === null) {
if (router.route !== '/login') {
router.replace('/login');
}
setIsAuthenticated(false);
} else if (session !== undefined) {
if (router.route === '/login') {
router.replace('/');
}
setIsAuthenticated(true);
}
}, [session]);
return isAuthenticated;
}

View File

@@ -1,6 +1,18 @@
import '@/styles/globals.css'
import type { AppProps } from 'next/app'
import { SessionProvider } from 'next-auth/react';
import { useState } from 'react';
import RefreshTokenHandler from '../components/refreshTokenHandler.js';
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
const [interval, setInterval] = useState(0);
return (
<SessionProvider session={pageProps.session} refetchInterval={interval}>
<Component {...pageProps} />
<RefreshTokenHandler setInterval={setInterval} />
</SessionProvider>
)
// return <Component {...pageProps} />
}

View File

@@ -0,0 +1,118 @@
import NextAuth from "next-auth/next";
import CredentialsProvider from "next-auth/providers/credentials";
import { signIn } from "next-auth/react";
const pubAPI = process.env.DIRECTUS_PUBLIC_API;
export const options = {
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
email: { label: "Email", type: "text" },
password: { label: "Mot de passe", type: "password" },
},
async authorize(credentials, req) {
const payload = {
email: credentials.email,
password: credentials.password,
};
const res = await fetch(pubAPI + "auth/login", {
method: "POST",
body: JSON.stringify(payload),
headers: { "Content-Type": "application/json" },
credentials: "include",
});
const user = await res.json();
// console.log(user)
if (!res.ok) {
throw new Error("Email ou mot de passe incorrect.");
}
if (res.ok && user) {
return user;
}
return null;
},
}),
],
session: {
jwt: true,
},
callbacks: {
async jwt({ token, user, account }) {
if (account && user) {
return {
...token,
accessToken: user.data.access_token,
expires: Date.now() + user.data.expires,
refreshToken: user.data.refresh_token,
error: user.data.error,
};
}
if (Date.now() < token.expires) {
return token;
}
const refreshed = await refreshAccessToken(token);
return await refreshed;
},
async session({ session, token }) {
session.user.accessToken = token.accessToken;
session.user.refreshToken = token.refreshToken;
session.user.expires = token.expires;
session.user.error = token.error;
return session;
},
},
secret: process.env.NEXTAUTH_SECRET,
pages: {
signIn: "/login",
},
debug: true,
};
async function refreshAccessToken(token) {
try {
const response = await fetch(pubAPI + "auth/refresh", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ refresh_token: token.refreshToken }),
credentials: "include",
});
const refreshedTokens = await response.json();
if (!response.ok) {
signIn();
}
if (response.ok && refreshedTokens) {
return {
...token,
accessToken: refreshedTokens.data.access_token,
expires: Date.now() + refreshedTokens.data.expires,
refreshToken: refreshedTokens.data.refresh_token,
};
}
} catch (error) {
console.log(
new Date().toUTCString() + " Error in refreshAccessToken:",
error
);
return {
...token,
error: "RefreshAccessTokenError",
};
}
}
const nextauthfunc = (req, res) => NextAuth(req, res, options);
export default nextauthfunc;

View File

@@ -32,7 +32,7 @@ export default function handler(req, res) {
function(err) {
if (err) {
msg={ msg: 'Err400D' };
// res.status(400).send({ msg: 'Err400D' });
res.status(400).send({ msg: 'Err400D' });
// return console.log(err.message);
}
// console.log('message');

View File

@@ -1,13 +1,21 @@
import Image from 'next/image'
import { signIn, signOut } from 'next-auth/react';
import NavBar from '../components/NavBar'
import { Inter } from 'next/font/google'
import { useSession } from "next-auth/react";
const inter = Inter({ subsets: ['latin'] })
// const { data: session } = useSession();
// console.log(session)
export default function Home() {
return (
<main>
<NavBar />
<br />
<button onClick={() => signIn('credentials', { email: 'user5@email.com', password: 'Simple2pass' })}>
Sign in
</button>
<button onClick={() => signOut()}>
Sign out
</button>
</main>
)
}

115
src/pages/login.jsx Normal file
View File

@@ -0,0 +1,115 @@
import { useState } from 'react';
import { signIn, getCsrfToken } from 'next-auth/react';
import { Formik, Field, ErrorMessage } from 'formik';
import * as Yup from 'yup';
import { useRouter } from 'next/router';
export default function SignIn({ csrfToken }) {
const router = useRouter();
const [error, setError] = useState(null);
return (
<>
<Formik
initialValues={{ email: '', password: '', tenantKey: '' }}
validationSchema={Yup.object({
email: Yup.string()
.max(30, 'Must be 30 characters or less')
.email('Invalid email address')
.required('Please enter your email'),
password: Yup.string().required('Please enter your password'),
})}
onSubmit={async (values, { setSubmitting }) => {
const res = await signIn('credentials', {
redirect: false,
email: values.email,
password: values.password,
callbackUrl: `${window.location.origin}`,
});
if (res?.error) {
setError(res.error);
} else {
setError(null);
}
if (res.url) router.push(res.url);
setSubmitting(false);
}}
>
{(formik) => (
<form onSubmit={formik.handleSubmit}>
<div
className="bg-red-400 flex flex-col items-center
justify-center min-h-screen py-2 shadow-lg">
<div className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
<input
name="csrfToken"
type="hidden"
defaultValue={csrfToken}
/>
<div className="text-red-400 text-md text-center rounded p-2">
{error}
</div>
<div className="mb-4">
<label
htmlFor="email"
className="uppercase text-sm text-gray-600 font-bold"
>
Email
<Field
name="email"
aria-label="enter your email"
aria-required="true"
type="text"
className="w-full bg-gray-300 text-gray-900 mt-2 p-3"
/>
</label>
<div className="text-red-600 text-sm">
<ErrorMessage name="email" />
</div>
</div>
<div className="mb-6">
<label
htmlFor="password"
className="uppercase text-sm text-gray-600 font-bold"
>
password
<Field
name="password"
aria-label="enter your password"
aria-required="true"
type="password"
className="w-full bg-gray-300 text-gray-900 mt-2 p-3"
/>
</label>
<div className="text-red-600 text-sm">
<ErrorMessage name="password" />
</div>
</div>
<div className="flex items-center justify-center">
<button
type="submit"
className="bg-green-400 text-gray-100 p-3 rounded-lg w-full"
>
{formik.isSubmitting ? 'Please wait...' : 'Sign In'}
</button>
</div>
</div>
</div>
</form>
)}
</Formik>
</>
);
}
// This is the recommended way for Next.js 9.3 or newer
export async function getServerSideProps(context) {
return {
props: {
csrfToken: await getCsrfToken(context),
},
};
}

22
src/pages/profile.jsx Normal file
View File

@@ -0,0 +1,22 @@
import { signIn, signOut, useSession } from 'next-auth/react'
export default function Home() {
const { data: session } = useSession()
console.log(session)
if (session) {
return (
<>
Signed in
<br />
<button onClick={() => signOut()}>Sign out</button>
</>
)
}
return (
<>
Not signed in <br />
<button onClick={() => signIn()}>Sign in</button>
</>
)
}