login
This commit is contained in:
18
src/components/refreshTokenHandler.js
Normal file
18
src/components/refreshTokenHandler.js
Normal 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
29
src/components/useAuth.js
Normal 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;
|
||||
}
|
||||
@@ -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} />
|
||||
}
|
||||
|
||||
118
src/pages/api/auth/[...nextauth].js
Normal file
118
src/pages/api/auth/[...nextauth].js
Normal 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;
|
||||
@@ -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');
|
||||
|
||||
@@ -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
115
src/pages/login.jsx
Normal 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
22
src/pages/profile.jsx
Normal 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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user