Next Auth authentication
Answered
SkippTekk posted this in #help-forum
So, i have been trying to figure this out all day. I'm freshly new to NextJS and im surprised i even got this far and always got confused on the session signup.
using Username,Password to login. That's working but can't get it to save to the session.
ideas?
using Username,Password to login. That's working but can't get it to save to the session.
ideas?
import type { NextAuthOptions } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import bcrypt from "bcrypt";
import User from '../../../../models/user'
export const options: NextAuthOptions = {
providers: [
CredentialsProvider({
name: "Credentials",
credentials: {
username: { label: 'Username', type: 'text', placeholder: 'Username' },
password: { label: 'Password', type: 'password', placeholder: 'Password' }
},
async authorize(credentials, req) {
const userCheck = await User.findOne({ where: { username: credentials?.username } })
if (userCheck) {
bcrypt.compare(credentials?.password, userCheck.hashPassword, function (err, result) {
if (result === true) {
console.log(JSON.stringify(userCheck))
return JSON.stringify(userCheck);
} else throw Error('Sorry, password doesn\'t match')
})
} else throw Error('Sorry, login failed')
}
})
],
session: {
strategy: 'jwt'
},
callbacks: {
async signIn({ user, account, profile, email, credentials }) {
return true
}
}
}
243 Replies
Well, i probably need to save the username, email and id to the session? Just new to this stuff so it will take me a bit to understand it.
cause right now im getting spammed with
@SkippTekk Well, i probably need to save the username, email and id to the session? Just new to this stuff so it will take me a bit to understand it.
So basically first thing that catches my eye is that you are throwing errors when the login fails. NextAuth actually expects you to just return
null
. If the login fails, you are supposed to return the user object (not a string). NextAuth will safe at lease email, username and image to the session by default.@SkippTekk cause right now im getting spammed with
This error tells me nothing tbh. Seems to be related to
bcrypt
That's weird as bcrypt is only called during the register and login. that's it
I just googled your error and bcrypt turned up several times. Maybe it's something else but the error just honestly doesn't tell much
then it's complaining about FS not being installed... but it is.
(╯°□°)╯︵ â”»â”â”»
i really wish i don't wanna restart this entire thing again :v
Where are you requiring fs?
No where.
It honestly could be my sequilite setup. as it popped up afterwords
I never used SQLite
Can you show the fs error?
not using SQLite 😛 using Mariadb with it
Alright then. I use MariaDB as well with Prisma
@SkippTekk Yea... i tried prisma. I couldn't use it like sequilize for example user.ROW
What does
user.ROW
do?basically using the database reply with a spacific ROW (like password for example) and compare my bcrypt
So in raw SQL something like
SELECT password FROM users
?Pretty much, for Prisma i was checking the username, then if it existed it would do the becrypt compare. couldn't get that part working so i converted over
That's how you select specific rows in Prisma
const user = await prisma.users.findFirst({
where: {
username: username
},
select: {
password: true
}
});
Then using it in bcrypt would be
bcrypt.compare(oldpassword, storePassword)
and that part was causing me issues.Wait, I'll send you my
authorize
function so you can see how I did itasync authorize(credentials, req) {
const { login, password } = credentials!;
const user = await prisma.user.findFirst({
where: {
OR: [
{ email: login },
{ username: login },
],
},
});
if (!user) return null;
if (await bcrypt.compare(password, user.password)) {
return {
id: user.id,
email: user.email,
username: user.username,
name: user.displayName,
isVerified: user.verified,
};
}
return null;
}
my guy.... where were you like 2 days ago when i was asking for help xD
@SkippTekk my guy.... where were you like 2 days ago when i was asking for help xD
Playing Ghost Recon if I remember correctly
Fair. that's a valide reply 😛
But as for now it needs a little
@ts-ignore
above it because "Type (...) => Promise<..> is not assignable to type (...) => Awaitable<...>
". Was too lazy to fix that. It works anyway🤷â€â™‚ï¸Ok well, mind if i snatch that idea? it's actually close to what i do.
wait... what's with the
credentials!
@SkippTekk wait... what's with the credentials!
It assumes that
credentials
exist. If you wanna be extra sure, you can put a if (!credentials) return null;
above it and leave the !
away in the deconstructor.I actually don't know if
credentials
can be undefined
or similar lolMY GUY i have been using findUnique instead of findFirst slams face on desk
yeah, just the thing with the user.password, the password was always red squigly underneath
Theoretically
@SkippTekk yeah, just the thing with the user.password, the password was always red squigly underneath
Would have to see the error to tell what was wrong. Does it work now?
just finished and cleared my cache
getting that when ever i load my dashboard part. it's supposed to throw me to the login page cause my session doesn't exist
Can you show me your dashboard code?
it's plain as fuck.
'use client'
import React from 'react'
import styles from './page.module.css';
import { redirect } from 'next/navigation';
import useSWR from 'swr';
import { options } from '../api/auth/[...nextauth]/options';
import { getServerSession } from 'next-auth';
export default async function Dashboard() {
const session = await getServerSession(options)
return (
<>
{session ? (
<div className={styles.container}>Dashboard {session.user}</div>
) : (
redirect('/api/auth/signin')
)
}
</>
)
}
i'm gonna expand it once i get this login shit working :v
Get rid of all the checking if the user is logged in
You wanna use middleware for that
i thought the middleware was that method.
The issue is btw that you are importing the
options
from NextAuth
into a client component@SkippTekk i thought the middleware was that method.
Nah. Middleware is way cooler
Get rid of this
import { options } from '../api/auth/[...nextauth]/options';
just updated it too
'use client'
import React from 'react'
import styles from './page.module.css';
export default async function Dashboard() {
return (
<div className={styles.container}>Dashboard {session.user}</div>
)
}
One sec
ok THERE we go dashboard loads properly now
This is what you want to use to get the session:
import { useSession } from 'next-auth/react';
const { data: session } = useSession();
React Context is unavailable in Server Components
(╯°□°)╯︵ â”»â”â”»
Well, before we so father in... Thank you for helping me with this.
What's the current full code?
import React from 'react'
import styles from './page.module.css';
import { useSession } from 'next-auth/react';
export default async function Dashboard() {
const { data: session } = useSession()
return (
<div className={styles.container}>Dashboard {session}</div>
)
}
but i can remove {session} and it goes away
Add the
'use client'
on top againor not xD
Error: async/await is not yet supported in Client Components, only Server Components. This error is often caused by accidentally adding `'use client'` to a module that was originally written for the server.
boom done
Should work now
If it does, we gonna set up the middleware
yes it does 🙂
Perfect. Now create a file called
middleware.ts
inside the same folder app
is in.so root directory
Sure
Here's some documentation you can read or not. We will basically just add two lines which will be self explaining: https://next-auth.js.org/configuration/nextjs#middleware
If you're ready, tell me
yeh ready. i have seen something like this
right now it's just dashboard, then later on i can add in more with it or just use like
/dashboard/*
to make it all?already got a NEXTAUTH_SECRET thing in
and it's in
.env.local
or should it be just .env
/dashboard*
or what you said. Not entirely sure atm@SkippTekk and it's in .env.local or should it be just .env
I think in
.env
but not 100% sure. If NextAuth itself doesn't already throw warnings about it not finding it, it should worknah it loads it, which is nice
and i got NEXTAUTH_URL set too http://localhost:/3000 for now
http://localhost:3000 you mean?
yes
brain is fried for coding so much in the past week xD
XD
Middleware is done?
export { default } from 'next-auth/middleware'
export const config = { matcher: ['/dashboard'] }
Looks fine but this will just protect the exact route
/dashboard
for now so /dashboard/settings
e.g. would still be accessable without being logged inyeh that's perfecty fine, once i make more pages i'll be securing it
Alright then. Should be either
/dashboard*
, /dashboard(.*)
or probably both workAnyway. Did you test it?
well, the page is still loading, just gotta enable it (i think)
Enable it?
oh fuck, register is breaking one sec
well, you can type, i just gotta figure out why this is broken so hard xD
How is it broken?
Stop importing
options
from your NextAuth
config XDYou don't need it
im nooooooot
import { React, useState } from 'react'
import style from './page.module.css'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import PasswordChecklist from 'react-password-checklist'
import bcrypt from 'bcrypt'
Ooooh, don't import bcrypt then
I'm using bcrypt :v
i don't send naked passwords
Yeah, but you don't wanna hash the password client side
(╯°□°)╯︵ â”»â”â”»
The connection between the client and the server is secured by SSL anyway. Hashing it is just for secure storing in the database
So you want to hash it on the server especially because you can check it there as well in case you want the password to have a specific format (yk, the include one number, one uppercase thing)
ahhh, alright well gimme a bit i gotta do my backend
Sure
there, i think i got it
Test it so that you know
One thing i HATE about this right now. is that prisma caches it, even though the databse doesn't have the username/email anymore it thinks it is :v
eh?
import { NextResponse } from 'next/server';
import crypto from 'node:crypto'
import { PrismaClient } from '@prisma/client';
import sendEmail from '../../../../components/mail/mailing';
import bcrypt from 'bcrypt'
export const POST = async (request) => {
const prisma = new PrismaClient()
const { username, email, password } = await request.json();
let verified = crypto.randomBytes(32).toString('hex');
const verifyEmail = `http://localhost:3001/verify/${verified}`;
const hashPassword = bcrypt.hash(password, 10)
const userExist = await prisma.users.findFirst({ where: { OR: [{ username }, { email }] } })
if (userExist === null) {
await prisma.users.create({ data: { username, email, hashPassword, verified } })
await sendEmail(email, 'Biscuits Industrial Signup verification', verifyEmail)
await prisma.$disconnect()
return new NextResponse('Success!', { status: 201 })
} else {
await prisma.$disconnect()
return new NextResponse("Sorry, username and or email already exists!", { status: 500 })
}
};
that verify email thing will be changed xD
@SkippTekk eh?
Change your
prisma.schema
file and when you are done, use npx prisma db push
in the terminal to sync your database according to your schema file and prisma will then generate a new client so that your typing will be up to dateBtw, using
db push
makes your changes irreversible. If you are not entirely sure about what you are doing, you can use migrate
. Docs: https://www.prisma.io/docs/concepts/components/prisma-migrate/get-startedmy database is setup the way i wanna have it anyways.
@SkippTekk my database is setup the way i wanna have it anyways.
Yeah, but
db push
will change your database to fit your schema fileSo in that case, make sure your schema file fits your database before you push
i have the mysql database linked and pulled cause it's setup already to what i would like to have 😛
so i basically did the schema setup, prisma db pull and generate. then boom it's ready.
Then your typing should be correct. Is your registration working again?
bcrypt hash is being annoying :v
DUR i didn't await it
Tell me when you tested everything so we can do the final touches to your login system which would be loading all additional information into the session/token
If I didn't miss anything
well, dashboard doesn't throw people around. so not sure about that one.
Can you show me your file structure?
You have a
src
folderAnd your
app
folder is inside thereYerp
that's how it was created
So your
middleware.ts
has to go there as wellAh, so it's in the layout.ts location
It has to be where the
app
(or pages
) folder isWait, no
Outside the
app
folderInside the
src
folderBut not inside the
app
folderIt should be
/src/middleware.ts
alright it doesn't like the /dashboard* or /*
Why not?
What did you change?
The matcher in the middleware is very trivial sadly
it's in /src folder and it's just /dashboard
damn it, now when i register, it throws the error cause it loops and just does the loop for the check xD
LOL, fix it
im tryin, im seeing errors in my console saying it's connection local BUT it actually does the registration xD
@ncls. I think it should actually be `/dashboard/:path*` then
Those should be it btw to protect
/dashboard
and every subpage of it. *
is zero or morePort 465?
yeah not even using that xD
funny thing is, it creates the account THEN throws that
Did you maybe add a wrong connection string or something somewhere in your code?
Maybe some leftovers from sequelize?
OH 465 is my email service
LOL it's cause i don't have the login creds in. One sec
gonna remove a .env file... not sure if .env or .env.local
there we go, that's fixed
Alright
To get all the info, I needed a while as well. I think 2 or 3 hours to find the solution lol so I'm just gonna give it to you
callbacks: {
jwt: async ({ token, user, account, profile, isNewUser }: any) => {
if (user) {
token.user = user;
}
if (account) {
token.accessToken = account.access_token;
token.refreshToken = account.refresh_token;
}
return token;
},
session: ({ session, token }: any) => {
token.accessToken
return {
...session,
user: {
...session.user,
...token.user,
accessToken: token.accessToken,
refreshToken: token.refreshToken,
},
};
},
},
user
is what your authorize
function returnsOn a successful login ofc
yea... i got nothing near that xD
I copy and pasted that as well once I found it XD
And modified it a tiny bit to fit my needs
well, hopefully it works the same way. basically using the same data, id, username,email and such :v
basically doing this to get Eve Online stuff working.
Huh?
That's another thing im gonna have to setup later in the future xD
im reacreating https://skipptekk.com basically
ok, i tried the login, i got no idea if it works xD
the fuck?
Interesting
404 or what?
that was the redirect to that page
Yeah, it's supposed to do that
and if i go to the dashboard it won't let me in xD
Can you reach the login?
You can't, right?
res i can
@SkippTekk Click to see attachment
You can? What's this then?
"use client"
import { React, useState } from 'react'
import style from './page.module.css'
import Link from 'next/link'
import { signIn } from 'next-auth/react'
const Login = () => {
const handleSubmit = async (e) => {
e.preventDefault()
const username = e.target[0].value
const password = e.target[1].value
signIn('Credentials', { username, password })
}
return (
<div className={style.container}>
Login :D
<form className={style.form} onSubmit={handleSubmit}>
<input
type='text'
placeholder='username'
className={style.input}
required
/>
<input
type='password'
placeholder='password'
className={style.input}
required
/>
<button className={style.button}>Login</button>
</form>
<Link href='/dashboard/register'>Need an account?</Link>
</div>
)
}
export default Login
that's a redirect to the login page of when i tried to login
So when you login, what happens?
just throws me back into the login page
Do this first and then we'll see if it still persists and why: https://next-auth.js.org/configuration/pages
i have
pages: ({
signIn: '/dashboard/login'
})
alreadyAlright then
So you login at
/dashboard/login
. And then it throws you to the same page again or to /api/auth/signin
?http://localhost:3000/dashboard/login?callbackUrl=http%3A%2F%2Flocalhost%3A3000%2Fdashboard%2Flogin
Oh, well then
yerp.
The
callbackUrl
is where it wants to redirect you after a successfull login. You're getting redirected to the login lolBut
I don't think that's the issue
@SkippTekk Click to see attachment
This right here. What kind of error is it?
It was website console
Yes. It's a failed request to your auth API
I wanna know why it failed
Hopefully you just passed incorrect credentials
Maybe check that lol
You can put a bunch of
console.log
s inside your authorize
function to check what exactly is happeningwell, my
authorize
it's got red squigly under it atm xDShow me
won't let me screen shot it, leaves too fast
ah fuck i probably forgot that
Just add a
// @ts-ignore
above itwould you like to do a video call
Alright
This was resolved in a DM video call Thank you!
Answer