nodejs and nextjs
Unanswered
SahLozk posted this in #help-forum
SahLozkOP
I have an api in node js, and an application in next js, I wanted to know how to do authentication, from node or next? since I have the authentication and the role system to set up, if anyone can give me more information, thank you
158 Replies
@SahLozk I have an api in node js, and an application in next js, I wanted to know how to do authentication, from node or next? since I have the authentication and the role system to set up, if anyone can give me more information, thank you
I think you could fetch your node api on nextjs and store the token/id in cookies
SahLozkOP
I created an authentication system on my node js api, I retrieve the local storage, the token, only how can I hide elements or routes, in relation to the role?
@SahLozk I created an authentication system on my node js api, I retrieve the local storage, the token, only how can I hide elements or routes, in relation to the role?
could you change the api to look for the token from cookie or header?
SahLozkOP
"use client"
import { useState } from 'react';
const Login = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState(null);
const handleSubmit = async (e: any) => {
e.preventDefault();
try {
const response = await fetch('http://localhost:3001/api/auth/signIn', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password }),
});
if (!response.ok) {
throw new Error('Invalid email or password');
}
const data = await response.json();
const { userWithoutPassword, token } = data;
const { role } = userWithoutPassword;
const roleName = role.name; // This retrieves the name of the role
// Store the JWT token and role name in local storage
localStorage.setItem('token', token);
localStorage.setItem('role', roleName);
// Rediriger l'utilisateur vers une page sécurisée
window.location.href = '/';
} catch (error: any) {
setError(error.message); // Utilisation du message d'erreur plutôt que de l'objet d'erreur
}
};
return (
<div>
<h1>Login</h1>
{error && <p>{error}</p>} {/* Rendu du message d'erreur */}
<form onSubmit={handleSubmit}>
<div>
<label>Email:</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
</div>
<div>
<label>Password:</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
</div>
<button type="submit">Login</button>
</form>
</div>
);
};
export default Login;when I fill out the form and send it to the api, I get the token in localstorage
are you going to do client side rendering?
SahLozkOP
how ?
if you store the token on localStorage, then you can't access it on the server
so its better to store it to cookie
SahLozkOP
ok, I cut it, but I can't get it back
@SahLozk ok, I cut it, but I can't get it back
what do you mean?
SahLozkOP
I store it in cookies but impossible to recover it
do I have to use next auth?
@SahLozk I store it in cookies but impossible to recover it
you can use
cookies() from next/header to access itcookies().get("token")@SahLozk do I have to use next auth?
yeah, you could use next-auth with credential provider
SahLozkOP
I had used next auth but I managed to put the token back in my header to make the request via my api (node)
@Ray yeah thats good
SahLozkOP
no since I couldn't make the request
@SahLozk no since I couldn't make the request
why? do you have any error?
SahLozkOP
wait, I'm going to do it again and I'll tell you again in a short time
ok
SahLozkOP
const BiblesPage: React.FC = () => {
const [searchQuery, setSearchQuery] = useState({
Article: '',
Designation: ''
});
const [searchResults, setSearchResults] = useState<ExcelDataMagasin[]>([]);
const [loading, setLoading] = useState(false);
const handleSearch = async () => {
setLoading(true);
setSearchResults([]);
try {
const token = localStorage.getItem('token'); // Récupérer le token JWT du local storage
if (!token) {
throw new Error('Token JWT non trouvé dans le local storage');
}
const queryParams = new URLSearchParams(searchQuery).toString();
const res = await fetch(`http://localhost:3001/api/bible/search?${queryParams}`, {
headers: {
'Authorization': `Bearer ${token}` // Inclure le token JWT dans les en-têtes de la requête
}
});
console.log({ headers: {
Authorization: `Bearer ${token}`,
}});
if (res.ok) {
const data = await res.json();
setSearchResults(data);
} else {
console.error('Erreur lors de la recherche:', res.statusText);
}
} catch (error) {
console.error('Erreur lors de la recherche:', error);
} finally {
setLoading(false);
}
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchQuery({
...searchQuery,
[e.target.name]: e.target.value
});
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
handleSearch();
};
return (
<div>
<h1 className="text-2xl font-bold mb-4">Liste de Bibles</h1>
<BiblesSearch
searchQuery={searchQuery}
handleChange={handleChange}
handleSubmit={handleSubmit}
loading={loading}
/>
{loading && <LoadingIndicator />}
{searchResults.length > 0 && <BiblesList BiblesList={searchResults} />}
</div>
);
};
export default BiblesPage;impossible to retrieve the token in this page to send it to the node js api
@SahLozk impossible to retrieve the token in this page to send it to the node js api
are you using app router or page router?
SahLozkOP
app router
could you try to fetch on server?
or you want to fetch on client?
SahLozkOP
make a query on my nodejs api on fetch(
http://localhost:3001/api/bible/search?${queryParams}@SahLozk make a query on my nodejs api on fetch(`http://localhost:3001/api/bible/search?${queryParams}`
could you show your next auth config?
SahLozkOP
on the nodejs ? or on the nextjs ?
nextjs
I want to see how to store the token from backend
then I will show you how to fetch on server from the page
SahLozkOP
import CredentialsProvider from "next-auth/providers/credentials";
export const authOptions = {
secret: process.env.NEXTAUTH_SECRET || "test",
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
email: {
label: "Email",
type: "email",
placeholder: "example@example.com",
},
password: { label: "Password", type: "password" },
},
async authorize(credentials, req) {
const response = await fetch(`http://localhost:3001/api/auth/signIn`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email: credentials?.email, // Change username to email
password: credentials?.password,
}),
});
const user = await response.json();
console.log(user);
if (response.ok && user) {
return user;
}
return null;
},
}),
],
};SahLozkOP
{
userWithoutPassword: {
id: 1,
firstname: 'test',
lastname: 'test',
email: 'test@gmail.com',
roleId: 1,
role: { id: 1, name: 'administrateur' }
},
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoxLCJmaXJzdG5hbWUiOiJ0ZXN0IiwibGFzdG5hbWUiOiJ0ZXN0IiwiZW1haWwiOiJ0ZXN0QGdtYWlsLmNvbSIsInJvbGVJZCI6MSwicm9sZSI6eyJpZCI6MSwibmFtZSI6ImFkbWluaXN0cmF0ZXVyIn19LCJpYXQiOjE3MTEyMjYxNDAsImV4cCI6MTcxMTIyOTc0MH0.xal4Xji26Qzah8oDR3OFYEsgb6XQedRLGvcMlR_hWk8'
}
userWithoutPassword: {
id: 1,
firstname: 'test',
lastname: 'test',
email: 'test@gmail.com',
roleId: 1,
role: { id: 1, name: 'administrateur' }
},
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoxLCJmaXJzdG5hbWUiOiJ0ZXN0IiwibGFzdG5hbWUiOiJ0ZXN0IiwiZW1haWwiOiJ0ZXN0QGdtYWlsLmNvbSIsInJvbGVJZCI6MSwicm9sZSI6eyJpZCI6MSwibmFtZSI6ImFkbWluaXN0cmF0ZXVyIn19LCJpYXQiOjE3MTEyMjYxNDAsImV4cCI6MTcxMTIyOTc0MH0.xal4Xji26Qzah8oDR3OFYEsgb6XQedRLGvcMlR_hWk8'
}
yes
ok wait
@SahLozk
import CredentialsProvider from "next-auth/providers/credentials";
export const authOptions = {
secret: process.env.NEXTAUTH_SECRET || "test",
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
email: {
label: "Email",
type: "email",
placeholder: "example@example.com",
},
password: { label: "Password", type: "password" },
},
async authorize(credentials, req) {
const response = await fetch(`http://localhost:3001/api/auth/signIn`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email: credentials?.email, // Change username to email
password: credentials?.password,
}),
});
const user = await response.json();
console.log(user);
if (response.ok && user) {
return user;
}
return null;
},
}),
],
};
callbacks: {
jwt({ token, user }) {
if (user) {
token.token = user.token;
}
return token;
},
session({ session, token }) {
if (token.token) {
session.user.token = token.token;
}
return session;
},
}add these to auth config
are you using next-auth v4 or v5?
SahLozkOP
"next-auth": "^4.24.7",
import CredentialsProvider from "next-auth/providers/credentials";
export const authOptions = {
secret: process.env.NEXTAUTH_SECRET || "test",
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
email: {
label: "Email",
type: "email",
placeholder: "example@example.com",
},
password: { label: "Password", type: "password" },
},
async authorize(credentials, req) {
const response = await fetch(`http://localhost:3001/api/auth/signIn`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email: credentials?.email, // Change username to email
password: credentials?.password,
}),
});
const user = await response.json();
console.log(user);
if (response.ok && user) {
return user;
}
return null;
},
}),
],
callbacks: {
jwt: async ({ token, user }) => {
// Add the user's token to the token object
if (user) {
token.token = user.token; // Assuming user.token contains the JWT
}
return token;
},
session: async ({ session, token }) => {
// Add the token to the session object
if (token?.token) {
session.token = token.token;
}
return session;
},
},
};Property 'token' does not exist on type 'User'.
@SahLozk
import CredentialsProvider from "next-auth/providers/credentials";
export const authOptions = {
secret: process.env.NEXTAUTH_SECRET || "test",
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
email: {
label: "Email",
type: "email",
placeholder: "example@example.com",
},
password: { label: "Password", type: "password" },
},
async authorize(credentials, req) {
const response = await fetch(`http://localhost:3001/api/auth/signIn`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email: credentials?.email, // Change username to email
password: credentials?.password,
}),
});
const user = await response.json();
console.log(user);
if (response.ok && user) {
return user;
}
return null;
},
}),
],
callbacks: {
jwt: async ({ token, user }) => {
// Add the user's token to the token object
if (user) {
token.token = user.token; // Assuming user.token contains the JWT
}
return token;
},
session: async ({ session, token }) => {
// Add the token to the session object
if (token?.token) {
session.token = token.token;
}
return session;
},
},
};
add this
import { DefaultSession } from "next-auth";
import { type JWT } from "next-auth/jwt";
declare module "next-auth" {
interface Session {
user: {
token: string;
} & DefaultSession["user"];
}
interface User {
token: string;
}
}
declare module "next-auth/jwt" {
/** Returned by the `jwt` callback and `getToken`, when using JWT sessions */
interface JWT {
/** OpenID ID Token */
token?: string;
}
}SahLozkOP
{
name: undefined,
email: undefined,
picture: undefined,
sub: undefined,
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoxLCJmaXJzdG5hbWUiOiJ0ZXN0IiwibGFzdG5hbWUiOiJ0ZXN0IiwiZW1haWwiOiJ0ZXN0QGdtYWlsLmNvbSIsInJvbGVJZCI6MSwicm9sZSI6eyJpZCI6MSwibmFtZSI6ImFkbWluaXN0cmF0ZXVyIn19LCJpYXQiOjE3MTEyMjc5MTgsImV4cCI6MTcxMTIzMTUxOH0.OhPl5IqoDVPfQro78pja03J7Ve_ZyGMU_fKo239kCEI'
}
name: undefined,
email: undefined,
picture: undefined,
sub: undefined,
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoxLCJmaXJzdG5hbWUiOiJ0ZXN0IiwibGFzdG5hbWUiOiJ0ZXN0IiwiZW1haWwiOiJ0ZXN0QGdtYWlsLmNvbSIsInJvbGVJZCI6MSwicm9sZSI6eyJpZCI6MSwibmFtZSI6ImFkbWluaXN0cmF0ZXVyIn19LCJpYXQiOjE3MTEyMjc5MTgsImV4cCI6MTcxMTIzMTUxOH0.OhPl5IqoDVPfQro78pja03J7Ve_ZyGMU_fKo239kCEI'
}
SahLozkOP
yes
can you show auth config again?
SahLozkOP
import CredentialsProvider from "next-auth/providers/credentials";
import { JWT } from "next-auth/jwt";
declare module "next-auth" {
interface Session {
token: string; // Adjust this based on your actual session structure
}
}
export const authOptions = {
secret: process.env.NEXTAUTH_SECRET || "test",
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
email: {
label: "Email",
type: "email",
placeholder: "example@example.com",
},
password: { label: "Password", type: "password" },
},
async authorize(credentials, req) {
const response = await fetch(`http://localhost:3001/api/auth/signIn`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email: credentials?.email,
password: credentials?.password,
}),
});
const user = await response.json();
if (response.ok && user) {
return user;
}
return null;
},
}),
],
callbacks: {
jwt: async ({ token, user }: { token: JWT; user: any }) => {
if (user && user.token) {
token.token = user.token;
}
return token;
},
session: async ({ session, token }: { session: any; token: JWT }) => {
if (token?.token) {
session.token = token.token;
}
return session;
},
},
};@SahLozk
import CredentialsProvider from "next-auth/providers/credentials";
import { JWT } from "next-auth/jwt";
declare module "next-auth" {
interface Session {
token: string; // Adjust this based on your actual session structure
}
}
export const authOptions = {
secret: process.env.NEXTAUTH_SECRET || "test",
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
email: {
label: "Email",
type: "email",
placeholder: "example@example.com",
},
password: { label: "Password", type: "password" },
},
async authorize(credentials, req) {
const response = await fetch(`http://localhost:3001/api/auth/signIn`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email: credentials?.email,
password: credentials?.password,
}),
});
const user = await response.json();
if (response.ok && user) {
return user;
}
return null;
},
}),
],
callbacks: {
jwt: async ({ token, user }: { token: JWT; user: any }) => {
if (user && user.token) {
token.token = user.token;
}
return token;
},
session: async ({ session, token }: { session: any; token: JWT }) => {
if (token?.token) {
session.token = token.token;
}
return session;
},
},
};
session: async ({ session, token }: { session: any; token: JWT }) => {
if (token?.token) {
session.user.token = token.token;
}
return session;
},@SahLozk
import CredentialsProvider from "next-auth/providers/credentials";
import { JWT } from "next-auth/jwt";
declare module "next-auth" {
interface Session {
token: string; // Adjust this based on your actual session structure
}
}
export const authOptions = {
secret: process.env.NEXTAUTH_SECRET || "test",
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
email: {
label: "Email",
type: "email",
placeholder: "example@example.com",
},
password: { label: "Password", type: "password" },
},
async authorize(credentials, req) {
const response = await fetch(`http://localhost:3001/api/auth/signIn`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email: credentials?.email,
password: credentials?.password,
}),
});
const user = await response.json();
if (response.ok && user) {
return user;
}
return null;
},
}),
],
callbacks: {
jwt: async ({ token, user }: { token: JWT; user: any }) => {
if (user && user.token) {
token.token = user.token;
}
return token;
},
session: async ({ session, token }: { session: any; token: JWT }) => {
if (token?.token) {
session.token = token.token;
}
return session;
},
},
};
then you should be able to get the token with
getServerSessionexport default async function Page() {
const session = await getServerSession(authOptions)
const token = session.user.token
return <pre>{JSON.stringify(session, null, 2)}</pre>
}SahLozkOP
import NextAuth from "next-auth";
import { authOptions } from "./options";
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };import CredentialsProvider from "next-auth/providers/credentials";
import { JWT } from "next-auth/jwt";
declare module "next-auth" {
interface Session {
token: string; // Adjust this based on your actual session structure
}
}
export const authOptions = {
secret: process.env.NEXTAUTH_SECRET || "test",
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
email: {
label: "Email",
type: "email",
placeholder: "example@example.com",
},
password: { label: "Password", type: "password" },
},
async authorize(credentials, req) {
const response = await fetch(`http://localhost:3001/api/auth/signIn`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email: credentials?.email,
password: credentials?.password,
}),
});
const user = await response.json();
if (response.ok && user) {
return user;
}
return null;
},
}),
],
callbacks: {
jwt: async ({ token, user }: { token: JWT; user: any }) => {
if (user && user.token) {
token.token = user.token;
}
return token;
},
session: async ({ session, token }: { session: any; token: JWT }) => {
if (token?.token) {
session.user.token = token.token;
}
return session;
},
},
};SahLozkOP
I can connect but when I want to put the token in the Bibles page, the request is not made since I do not have the token
"use client"
import { useState } from 'react';
import { ExcelDataMagasin } from '../types/db';
import LoadingIndicator from '@/components/LoadingIndicator/LoadingIndicator';
import BiblesList from '@/components/BiblesList/BiblesList';
import BiblesSearch from '@/components/BiblesSearch/BiblesSearch';
const BiblesPage: React.FC = () => {
const [searchQuery, setSearchQuery] = useState({
Article: '',
Designation: ''
});
const [searchResults, setSearchResults] = useState<ExcelDataMagasin[]>([]);
const [loading, setLoading] = useState(false);
const handleSearch = async () => {
setLoading(true);
setSearchResults([]);
try {
const token = localStorage.getItem('token'); // Récupérer le token JWT du local storage
if (!token) {
throw new Error('Token JWT non trouvé dans le local storage');
}
const queryParams = new URLSearchParams(searchQuery).toString();
const res = await fetch(`http://localhost:3001/api/bible/search?${queryParams}`, {
headers: {
'Authorization': `Bearer ${token}` // Inclure le token JWT dans les en-têtes de la requête
}
});
console.log({ headers: {
Authorization: `Bearer ${token}`,
}});
if (res.ok) {
const data = await res.json();
setSearchResults(data);
} else {
console.error('Erreur lors de la recherche:', res.statusText);
}
} catch (error) {
console.error('Erreur lors de la recherche:', error);
} finally {
setLoading(false);
}
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchQuery({
...searchQuery,
[e.target.name]: e.target.value
});
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
handleSearch();
};
return (SahLozkOP
"use client"
import { useState } from 'react';
import { ExcelDataMagasin } from '../types/db';
import LoadingIndicator from '@/components/LoadingIndicator/LoadingIndicator';
import BiblesList from '@/components/BiblesList/BiblesList';
import BiblesSearch from '@/components/BiblesSearch/BiblesSearch';
import { useSession } from 'next-auth/react';
const BiblesPage: React.FC = () => {
const [searchQuery, setSearchQuery] = useState({
Article: '',
Designation: ''
});
const [searchResults, setSearchResults] = useState<ExcelDataMagasin[]>([]);
const [loading, setLoading] = useState(false);
const { data: session, status } = useSession();
const handleSearch = async () => {
setLoading(true);
setSearchResults([]);
try {
if (status === 'unauthenticated' || !session) {
throw new Error('User not authenticated');
}
console.log(session);
const token = session.user?.token;
const queryParams = new URLSearchParams(searchQuery).toString();
const res = await fetch(`http://localhost:3001/api/bible/search?${queryParams}`, {
headers: {
'Authorization': `Bearer ${token}`
}
});
if (res.ok) {
const data = await res.json();
setSearchResults(data);
} else {
console.error('Erreur lors de la recherche:', res.statusText);
}
} catch (error) {
console.error('Erreur lors de la recherche:', error);
} finally {
setLoading(false);
}
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchQuery({
...searchQuery,
[e.target.name]: e.target.value
});
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
handleSearch();
};
return ({
user: {
name: undefined,
email: undefined,
image: undefined,
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoxLCJmaXJzdG5hbWUiOiJ0ZXN0IiwibGFzdG5hbWUiOiJ0ZXN0IiwiZW1haWwiOiJ0ZXN0QGdtYWlsLmNvbSIsInJvbGVJZCI6MSwicm9sZSI6eyJpZCI6MSwibmFtZSI6ImFkbWluaXN0cmF0ZXVyIn19LCJpYXQiOjE3MTEyMjgzMDcsImV4cCI6MTcxMTIzMTkwN30.pOWwWQ-9nEeyyPYF1DkTYU16yjCt9PNE9dvWaPCmvUs'
}
}
user: {
name: undefined,
email: undefined,
image: undefined,
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoxLCJmaXJzdG5hbWUiOiJ0ZXN0IiwibGFzdG5hbWUiOiJ0ZXN0IiwiZW1haWwiOiJ0ZXN0QGdtYWlsLmNvbSIsInJvbGVJZCI6MSwicm9sZSI6eyJpZCI6MSwibmFtZSI6ImFkbWluaXN0cmF0ZXVyIn19LCJpYXQiOjE3MTEyMjgzMDcsImV4cCI6MTcxMTIzMTkwN30.pOWwWQ-9nEeyyPYF1DkTYU16yjCt9PNE9dvWaPCmvUs'
}
}
@SahLozk Click to see attachment
wrap children on app/layout.tsx with SessionProvider
SahLozkOP
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { AppRouterCacheProvider } from "@mui/material-nextjs/v14-appRouter";
import theme from "@/utils/theme";
import { Container, LinearProgress, ThemeProvider } from "@mui/material";
import { Suspense } from "react";
import NavBar from "@/components/NavBar/NavBar";
import { CartProvider } from "@/components/CartContext/CartContext";
import { authOptions } from "./api/auth/[...nextauth]/options";
import { getServerSession } from "next-auth";
import { SessionProvider } from "next-auth/react";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const session = await getServerSession(authOptions);
console.log(session);
console.log("session");
return (
<html lang="fr">
<body className={inter.className}>
<AppRouterCacheProvider>
<SessionProvider session={session}>
<ThemeProvider theme={theme}>
<Suspense fallback={<LinearProgress color="primary" />}>
<CartProvider>
<NavBar />
<Container>{children}</Container>
</CartProvider>
</Suspense>
</ThemeProvider>
</SessionProvider>
</AppRouterCacheProvider>
</body>
</html>
);
}SahLozkOP
Unhandled Runtime Error
Error: React Context is unavailable in Server Components
Error: React Context is unavailable in Server Components
@SahLozk Unhandled Runtime Error
Error: React Context is unavailable in Server Components
create a file
'use client'
export { SessionProvider } from "next-auth/react";@Ray create a file
ts
'use client'
export { SessionProvider } from "next-auth/react";
then import this to app/layout.tsx
SahLozkOP
not successful ^^
I can't get there
@SahLozk not successful ^^
what error you get now?
SahLozkOP
je dois le mettre ou ?
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { AppRouterCacheProvider } from "@mui/material-nextjs/v14-appRouter";
import theme from "@/utils/theme";
import { Container, LinearProgress, ThemeProvider } from "@mui/material";
import { Suspense } from "react";
import NavBar from "@/components/NavBar/NavBar";
import { CartProvider } from "@/components/CartContext/CartContext";
import { authOptions } from "./api/auth/[...nextauth]/options";
import { getServerSession } from "next-auth";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const session = await getServerSession(authOptions);
console.log(session);
console.log("session");
return (
<html lang="fr">
<body className={inter.className}>
<AppRouterCacheProvider>
<ThemeProvider theme={theme}>
<Suspense fallback={<LinearProgress color="primary" />}>
<CartProvider>
<NavBar />
<Container>{children}</Container>
</CartProvider>
</Suspense>
</ThemeProvider>
</AppRouterCacheProvider>
</body>
</html>
);
}SahLozkOP
I don't know where to put it
// app/provider.tsx
'use client'
export { SessionProvider } from "next-auth/react";
// app/layout.tsx
import { SessionProvider } from './provider';you can create it anywhere you like but you have to re-export with 'use client' on top
SahLozkOP
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { AppRouterCacheProvider } from "@mui/material-nextjs/v14-appRouter";
import theme from "@/utils/theme";
import { Container, LinearProgress, ThemeProvider } from "@mui/material";
import { Suspense } from "react";
import NavBar from "@/components/NavBar/NavBar";
import { CartProvider } from "@/components/CartContext/CartContext";
import { authOptions } from "./api/auth/[...nextauth]/options";
import { getServerSession } from "next-auth";
import { SessionProvider } from './provider';
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const session = await getServerSession(authOptions);
console.log(session);
console.log("session");
return (
<html lang="fr">
<body className={inter.className}>
<AppRouterCacheProvider>
<ThemeProvider theme={theme}>
<SessionProvider session={session}>
<Suspense fallback={<LinearProgress color="primary" />}>
<CartProvider>
<NavBar />
<Container>{children}</Container>
</CartProvider>
</Suspense>
</SessionProvider>
</ThemeProvider>
</AppRouterCacheProvider>
</body>
</html>
);
}SahLozkOP
const BiblesPage: React.FC = () => {
const [searchQuery, setSearchQuery] = useState({
Article: '',
Designation: ''
});
const [searchResults, setSearchResults] = useState<ExcelDataMagasin[]>([]);
const [loading, setLoading] = useState(false);
const handleSearch = async () => {
setLoading(true);
setSearchResults([]);
try {
const token = localStorage.getItem('token'); // Récupérer le token JWT du local storage
if (!token) {
throw new Error('Token JWT non trouvé dans le local storage');
}
const queryParams = new URLSearchParams(searchQuery).toString();
const res = await fetch(`http://localhost:3001/api/bible/search?${queryParams}`, {
headers: {
'Authorization': `Bearer ${token}` // Inclure le token JWT dans les en-têtes de la requête
}
});
console.log({ headers: {
Authorization: `Bearer ${token}`,
}});
if (res.ok) {
const data = await res.json();
setSearchResults(data);
} else {
console.error('Erreur lors de la recherche:', res.statusText);
}
} catch (error) {
console.error('Erreur lors de la recherche:', error);
} finally {
setLoading(false);
}
};How do I get the token here?
SahLozkOP
Property 'token' does not exist on type '{ name?: string | null | undefined; email?: string | null | undefined; image?: string | null | undefined; }'.ts(2339)
import { useState } from 'react';
import { ExcelDataMagasin } from '../types/db';
import { useSession } from 'next-auth/react';
import LoadingIndicator from '@/components/LoadingIndicator/LoadingIndicator';
import BiblesList from '@/components/BiblesList/BiblesList';
import BiblesSearch from '@/components/BiblesSearch/BiblesSearch';
const BiblesPage: React.FC = () => {
const { data: session } = useSession();
const token = session?.user?.token;
const [searchQuery, setSearchQuery] = useState({
Article: '',
Designation: ''
});
const [searchResults, setSearchResults] = useState<ExcelDataMagasin[]>([]);
const [loading, setLoading] = useState(false);
const handleSearch = async () => {
setLoading(true);
setSearchResults([]);
try {
const queryParams = new URLSearchParams(searchQuery).toString();
const res = await fetch(`http://localhost:3001/api/bible/search?${queryParams}`, {
headers: {
Authorization: `Bearer ${token}` // Add the token to the request header
}
});
if (res.ok) {
const data = await res.json();
setSearchResults(data);
} else {
console.error('Erreur lors de la recherche:', res.statusText);
}
} catch (error) {
console.error('Erreur lors de la recherche:', error);
} finally {
setLoading(false);
}
};SahLozkOP
ok it works but I don't know why I have this:
Property 'token' does not exist on type '{ name?: string | null | undefined; email?: string | null | undefined; image?: string | null | undefined; }'.ts(2339)
any
Property 'token' does not exist on type '{ name?: string | null | undefined; email?: string | null | undefined; image?: string | null | undefined; }'.ts(2339)
any
SahLozkOP
is the next-auth.d.ts file useful?
@Ray use `useSession` hook in client component
SahLozkOP
It's okay, it's fixed, tomorrow I will make some changes, I thank you for your help, have a good evening
SahLozkOP
@Ray
what is the best method to recover the role? from the token? or store the role directly in the cookie?
what is the best method to recover the role? from the token? or store the role directly in the cookie?
@SahLozk <@743561772069421169>
what is the best method to recover the role? from the token? or store the role directly in the cookie?
you could store it to the session like you did with token
@Ray you could store it to the session like you did with token
SahLozkOP
ok that's good, how can I retrieve it now to make comparisons so as not to display components or others depending on whether the user is connected and if he has the right role?
@SahLozk ok that's good, how can I retrieve it now to make comparisons so as not to display components or others depending on whether the user is connected and if he has the right role?
use
getServerSession on server and useSession hook on clientSahLozkOP
do you have an example?
SahLozkOP
how can I display the Link div only if there is a token and if the user has the administrator role?
@Ray
import Link from "next/link";
export default function Home() {
return (
<div className="container mx-auto mt-10">
<h1 className="text-3xl font-bold mb-8">Page d'accueil</h1>
<div className="grid grid-cols-2 gap-4">
<Link href="/bibles">
<div className="bg-gray-100 rounded-lg p-6 cursor-pointer">
<h2 className="text-xl font-bold mb-2">Bible</h2>
<p className="text-gray-700 mb-4">
Cliquez ici pour accéder à la page de la Bible Magasin.
</p>
</div>
</Link>
</div>
);
}@Ray
SahLozkOP
and can we make a middleware in relation to its role depending on the pages that the user can access ?
SahLozkOP
@Ray, In the basic page.tsx (home) file, can we make a useSession, or do I have to make a component and import it into it?
@SahLozk <@743561772069421169>, In the basic page.tsx (home) file, can we make a useSession, or do I have to make a component and import it into it?
you can get the session with
getServerSession like this in server componentimport Link from "next/link";
import { getServerSession } from "next-auth";
export default async function Home() {
const session = await getServerSession();
const role = session?.user.role;
return (
<div className="container mx-auto mt-10">
<h1 className="text-3xl font-bold mb-8">Page d'accueil</h1>
<div className="grid grid-cols-2 gap-4">
<Link href="/bibles">
<div className="bg-gray-100 rounded-lg p-6 cursor-pointer">
<h2 className="text-xl font-bold mb-2">Bible</h2>
<p className="text-gray-700 mb-4">
Cliquez ici pour accéder à la page de la Bible Magasin.
</p>
</div>
</Link>
</div>
</div>
);
}SahLozkOP
undefined
@SahLozk undefined
try
import { authOptions } from 'your-auth-option';
const session = await getServerSession(authOptions)SahLozkOP
yeah, the big boss^^
SahLozkOP
@Ray how can you do to automatically redirect to a user who has an account and not a good role but who put the address which is not supposed to access ?
SahLozkOP
I already put that
export { default } from "next-auth/middleware";
export const config = { matcher: ["/importation", "/historiqueBonSortie"] };@SahLozk I already put that
export { default } from "next-auth/middleware";
export const config = { matcher: ["/importation", "/historiqueBonSortie"] };
add this to your authOption inside the
callbacks authorized({ req , token }) {
if (req.nextUrl.pathname.startWith("/some-url") && token.role !== "admin") return false
if(token) return true // If there is a token, the user is authenticated
return false // this will redirect to login
}SahLozkOP
I have 3 types of roles, a user / storekeeper / administrator
yeah you could check the role in the authorized function
SahLozkOP
Where should I put it?
export const authOptions = {
secret: process.env.NEXTAUTH_SECRET,
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
email: {
label: "Email",
type: "email",
placeholder: "example@example.com",
},
password: { label: "Password", type: "password" },
},
async authorize(credentials, req) {
const response = await fetch(`http://localhost:3001/api/auth/signIn`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email: credentials?.email,
password: credentials?.password,
}),
});
const data = await response.json();
console.log(data );
if (response.ok && data ) {
const { userWithoutPassword, token } = data;
return { ...userWithoutPassword, token, role: userWithoutPassword.role.name };
}
return null;
},
}),
],
callbacks: {
jwt: async ({ token, user }: { token: JWT; user: any }) => {
if (user && user.token) {
token.token = user.token;
token.role = user.role; // Stockage du rôle dans le token JWT
}
return token;
},
session: async ({ session, token }: { session: any; token: JWT }) => {
if (token?.token) {
session.user.token = token.token;
session.user.role = token.role; // Stockage du rôle dans la session
}
return session;
},
},
};SahLozkOP
callbacks: {
jwt: async ({ token, user }: { token: JWT; user: any }) => {
if (user && user.token) {
token.token = user.token;
token.role = user.role; // Stockage du rôle dans le token JWT
}
return token;
},
session: async ({ session, token }: { session: any; token: JWT }) => {
if (token?.token) {
session.user.token = token.token;
session.user.role = token.role; // Stockage du rôle dans la session
}
return session;
},
authorized({ req , token }: { req: any , token: JWT }) {
if (req.nextUrl.pathname.startWith("/some-url") && token.role !== "admin") return false
if(token) return true // If there is a token, the user is authenticated
return false // this will redirect to login
}SahLozkOP
I don't understand how it works ^^
if the role is allowed, return true. otherwise return false
SahLozkOP
I connected with a user account and I have access to the /import page (which I should not have the right to)
then return false when you are on /import page
SahLozkOP
already this is where I put the code
export const authOptions = {
secret: process.env.NEXTAUTH_SECRET,
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
email: {
label: "Email",
type: "email",
placeholder: "example@example.com",
},
password: { label: "Password", type: "password" },
},
async authorize(credentials, req) {
const response = await fetch(`http://localhost:3001/api/auth/signIn`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
email: credentials?.email,
password: credentials?.password,
}),
});
const data = await response.json();
console.log(data );
if (response.ok && data ) {
const { userWithoutPassword, token } = data;
return { ...userWithoutPassword, token, role: userWithoutPassword.role.name };
}
return null;
},
}),
],
callbacks: {
jwt: async ({ token, user }: { token: JWT; user: any }) => {
if (user && user.token) {
token.token = user.token;
token.role = user.role; // Stockage du rôle dans le token JWT
}
return token;
},
session: async ({ session, token }: { session: any; token: JWT }) => {
if (token?.token) {
session.user.token = token.token;
session.user.role = token.role; // Stockage du rôle dans la session
}
return session;
},
authorized({ req , token }: { req: any , token: JWT }) {
if (req.nextUrl.pathname.startWith("/") && token.role !== "administrateur") return false
if(token) return true // If there is a token, the user is authenticated
return false // this will redirect to login
}
},
};and page /import
import ImportExcelBible from "../../components/ImportExcelBible/ImportExcelBible";
import ImportExcelImportation from "../../components/ImportExcelImputation/ImportExcelImputation";
export default function Importation() {
return (
<>
<div className="mt-10">
<ImportExcelBible />
</div>
<div className="mt-10">
<ImportExcelImportation />
</div>
</>
);
}req.nextUrl.pathname.startWith("/")
you are checking "/" only
you are checking "/" only
SahLozkOP
ah, I have to put /import?
yes
SahLozkOP
if I have several links and several roles?
you have to check all of it
const adminRoutes = ["/1", "/2", "/3"]
if (adminRoutes.includes(req.nextUrl.pathname)) {
switch(token.role) {
case "admin":
case "moderator":
return true
default:
return false
}
}SahLozkOP
authorized({ req, token }: { req: any; token: JWT }) {
const adminRoutes = ["/importation", "/2", "/3"];
if (adminRoutes.includes(req.nextUrl.pathname)) {
switch (token.role) {
case "administrateur":
case "magasinier":
return true;
default:
return false;
}
}
},like that?
it does not work
you can still see the page?
SahLozkOP
I see the page with a user account when I shouldn't
@SahLozk I see the page with a user account when I shouldn't
try this and see what happen
authorized({ req, token }: { req: any; token: JWT }) {
return false
}SahLozkOP
there must be things missing, it doesn't matter
still see the page?
SahLozkOP
yes
@SahLozk yes
ok remove that and do this in middleware.ts instead
import { withAuth } from "next-auth/middleware";
import { NextResponse } from "next/server";
export default withAuth((req) => {
const adminRoutes = ["/importation", "/2", "/3"];
if (adminRoutes.includes(req.nextUrl.pathname)) {
switch (req.nextauth.token.role) {
case "administrateur":
case "magasinier":
return NextResponse.redirect(new URL("/login", req.url));
default:
return null;
}
}
});SahLozkOP
import { withAuth } from "next-auth/middleware";
import { NextResponse } from "next/server";
export default withAuth((req) => {
const adminRoutes = ["/importation", "/2", "/3"];
if (adminRoutes.includes(req.nextUrl.pathname)) {
switch (req.nextauth?.token?.role) {
// case "administrateur":
// case "magasinier":
case "utilisateur":
return NextResponse.redirect(new URL("/", req.url));
default:
return null;
}
}
});I don't think it's the best method but it seems to work, but now what if I only want /links on the admin role?
this is the best method imo
then add another check
SahLozkOP
import { withAuth } from "next-auth/middleware";
import { NextResponse } from "next/server";
export default withAuth((req) => {
const adminRoutes = ["/importation", "/historiqueBonSortie"];
const magasinierRoutes = ["/importation", "/historiqueBonSortie"];
if (adminRoutes.includes(req.nextUrl.pathname)) {
switch (req.nextauth?.token?.role) {
// case "administrateur":
// case "magasinier":
case "utilisateur":
return NextResponse.redirect(new URL("/", req.url));
default:
return null;
}
}
if (magasinierRoutes.includes(req.nextUrl.pathname)) {
switch (req.nextauth?.token?.role) {
// case "administrateur":
case "magasinier":
// case "utilisateur":
return NextResponse.redirect(new URL("/", req.url));
default:
return null;
}
}
});like this?
yes
SahLozkOP
doesn't work xD
oh yes ^^
but it works oddly
but it works oddly
what do you mean?
SahLozkOP
you cannot put two / links on two conditions you have to create a special one and add the two roles, if you do not want to block certain / links to others
example
const adminRoutes = ["/importation", "/historiqueBonSortie"];
const magasinierRoutes = ["/users"];
if (adminRoutes.includes(req.nextUrl.pathname)) {
switch (req.nextauth?.token?.role) {
// case "administrateur":
case "magasinier":
case "utilisateur":
return NextResponse.redirect(new URL("/", req.url));
default:
return null;
}
}
if (magasinierRoutes.includes(req.nextUrl.pathname)) {
switch (req.nextauth?.token?.role) {
// case "administrateur":
case "magasinier":
// case "utilisateur":
return NextResponse.redirect(new URL("/", req.url));
default:
return null;
}
}on the other hand, I have to log in, even though I have to access pages without being logged in.
if (!req.nextauth.token) return NextResponse.redirect(new URL("/login", req.url));
SahLozkOP
nope
what do you mean no?
@Ray if (!req.nextauth.token) return NextResponse.redirect(new URL("/login", req.url));
put this line at the first
SahLozkOP
does not work
@SahLozk I already put that
export { default } from "next-auth/middleware";
export const config = { matcher: ["/importation", "/historiqueBonSortie"] };
because the you have a matcher which only run the middleware on "/importation" and "/historiqueBonSortie"
SahLozkOP
we are forced to connect with the middleware configuration therefore
SahLozkOP
I did not succeed