Is my api safe?
Unanswered
Lithuanian Hound posted this in #help-forum
Lithuanian HoundOP
Hello! I have finished working on my project, I'm trying to make it now a little more secure. I'm by no means an expert in any of these things but quite opposite. I wanted to ask if my fetch function as well as api is secure enough to prevent any spoofing? This is one of the fetch functions, there's a few similar to this
My fetch in client-sided component
My api in /app/api :
My fetch in client-sided component
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
try {
const response = await fetch(`/api/creator/${userId}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(character),
});
const text = await response.text();
const data = text ? JSON.parse(text) : null;
if (response.ok) {
if (data && data.id) {
console.log("Character created");
const newCharacterId = data.id;
setCharacterId(newCharacterId);
setBackgroundPath(`/api/card/background/${newCharacterId}`);
if (selectedFile) {
await uploadFile(selectedFile, newCharacterId);
}
} else {
console.error("Invalid response data or characterId missing");
}
} else {
console.error(data ? data.error : "An error occurred");
}
} catch (error) {
console.error(error);
}
};My api in /app/api :
export async function POST(request: NextRequest) {
const userId = request.nextUrl.pathname.split('/').pop();
if (!userId) {
return NextResponse.json({ error: 'User ID not found' }, { status: 400 });
}
const requestBody = await request.text();
const { name, description, card, universe, customLink } = JSON.parse(requestBody);
if (!name || !universe) {
return NextResponse.json({ error: 'Missing required fields' }), { status: 400 };
}
const existingCharactersCount = await prisma.character.count({
where: {
userId,
},
});
if (existingCharactersCount >= 5) {
return NextResponse.json({ error: 'User can only create 5 characters' }, { status: 400 });
}
const predefinedUniverses = ['WOW', 'FFXIV', 'DND'];
console.log('Parsed request body 2:', { name, description, card, universe, customLink });
if (!universe || (!predefinedUniverses.includes(universe) && universe.length < 3)) {
return NextResponse.json({ error: 'Invalid universe value' }), { status: 400 };
}
try {
const character = await prisma.character.create({
data: {
name,
description,
card,
universe,
userId,
customLink,
},
});
const updatedCharacter = await prisma.character.update({
where: { id: character.id },
data: { customLink: character.id.toString() },
});
console.log('Character created successfully:', character);
return NextResponse.json(updatedCharacter, { status: 201 });
} catch (error) {
console.error('Error creating character:', error);
return NextResponse.json({ error: 'An error occurred while creating the character' }), { status: 500 };
}
}4 Replies
Lithuanian HoundOP
Or should I pass a random token that retrieves it in API? Or is there any good practice worth knowing?
if it's your toy hobby project, it should be okay, but once userId was exposed in some pages, every body would call the api.
in general, real world, we have authentication and authorization layer such as Auth.js along with cookie. (e.g. Gmail, X, Discord etc.)
in general, real world, we have authentication and authorization layer such as Auth.js along with cookie. (e.g. Gmail, X, Discord etc.)
@tafutada777 if it's your toy hobby project, it should be okay, but once userId was exposed in some pages, every body would call the api.
in general, real world, we have authentication and authorization layer such as Auth.js along with cookie. (e.g. Gmail, X, Discord etc.)
Lithuanian HoundOP
Yes I'm using next-auth. I'm using callback on a session and then fetching the session to provide userId.
Lithuanian HoundOP
I saw some examples have strategy to jwt specified and cookies in their next-auth route. I'm not doing this, it's hadled by default right?
export const authOptions: NextAuthOptions = {
adapter: PrismaAdapter(prisma),
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID as string,
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
authorization: {
params: {
prompt: "consent",
access_type: "offline",
response_type: "code",
},
},
}),
DiscordProvider({
clientId: process.env.DISCORD_CLIENT_ID as string,
clientSecret: process.env.DISCORD_CLIENT_SECRET as string,
}),
TwitterProvider({
clientId: process.env.TWITTER_CLIENT_ID as string,
clientSecret: process.env.TWITTER_CLIENT_SECRET as string,
}),
],
callbacks: {
async session({ session, user }) {
const prismaUser = await prisma.user.findUnique({
where: { id: user.id.toString() },
});
if (prismaUser) {
session.user = {
...session.user,
id: prismaUser.id,
isAdmin: prismaUser.isAdmin,
name: prismaUser.name!,
background: prismaUser.background || undefined,
profile: prismaUser.profile,
};
}
return session;
},
async signIn({ user, account, profile }) {
const email = profile?.email;
let newName = profile?.name || "DefaultName";
let existingUser = await prisma.user.findUnique({
where: { name: newName },
});
while (existingUser) {
newName = await assignUniqueName();
existingUser = await prisma.user.findUnique({
where: { name: newName },
});
}
user.name = newName;
user.email = email;
console.log("Google SignIn Callback - user:", user);
console.log("Google SignIn Callback - account:", account);
console.log("Google SignIn Callback - profile:", profile);
return true;
},
},
};
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };