Next.js Discord

Discord Forum

Next Auth authentication

Answered
SkippTekk posted this in #help-forum
Open in Discord
Avatar
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?
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
        }
    }
}
Answered by SkippTekk
This was resolved in a DM video call Thank you!
View full answer

243 Replies

Avatar
Save what exactly to the session?
Avatar
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
Image
Avatar
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.
This error tells me nothing tbh. Seems to be related to bcrypt
Avatar
That's weird as bcrypt is only called during the register and login. that's it
Avatar
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
Avatar
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
Avatar
Where are you requiring fs?
Avatar
No where.
It honestly could be my sequilite setup. as it popped up afterwords
Avatar
I never used SQLite
Can you show the fs error?
Avatar
not using SQLite 😛 using Mariadb with it
Avatar
Alright then. I use MariaDB as well with Prisma
Avatar
Yea... i tried prisma. I couldn't use it like sequilize for example user.ROW
Image
Avatar
What does user.ROW do?
Avatar
basically using the database reply with a spacific ROW (like password for example) and compare my bcrypt
Avatar
So in raw SQL something like SELECT password FROM users?
Avatar
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
Avatar
That's how you select specific rows in Prisma
const user = await prisma.users.findFirst({
  where: {
    username: username
  },
  select: {
    password: true
  }
});
Avatar
Then using it in bcrypt would be
bcrypt.compare(oldpassword, storePassword)
and that part was causing me issues.
Avatar
Wait, I'll send you my authorize function so you can see how I did it
async 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;
}
Avatar
my guy.... where were you like 2 days ago when i was asking for help xD
Avatar
Playing Ghost Recon if I remember correctly
Avatar
Fair. that's a valide reply 😛
Avatar
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🤷‍♂️
Avatar
Ok well, mind if i snatch that idea? it's actually close to what i do.
Avatar
Sure, go ahead
Avatar
wait... what's with the credentials!
Avatar
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 lol
Avatar
MY GUY i have been using findUnique instead of findFirst slams face on desk
Avatar
Should work too
Avatar
yeah, just the thing with the user.password, the password was always red squigly underneath
Avatar
Theoretically
Would have to see the error to tell what was wrong. Does it work now?
Avatar
just finished and cleared my cache
Image
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
Avatar
Can you show me your dashboard code?
Avatar
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
Avatar
Get rid of all the checking if the user is logged in
You wanna use middleware for that
Avatar
i thought the middleware was that method.
Avatar
The issue is btw that you are importing the options from NextAuth into a client component
Nah. Middleware is way cooler
Avatar
Image
Image
Avatar
Get rid of this import { options } from '../api/auth/[...nextauth]/options';
Avatar
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>

    )
}
Avatar
One sec
Avatar
Image
ok THERE we go dashboard loads properly now
Avatar
This is what you want to use to get the session:
import { useSession } from 'next-auth/react';

const { data: session } = useSession();
Avatar
React Context is unavailable in Server Components
(╯°□°)╯︵ ┻━┻
Well, before we so father in... Thank you for helping me with this.
Avatar
What's the current full code?
Avatar
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
Avatar
Add the 'use client' on top again
Avatar
or 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.
Avatar
Oooooh
Didn't see it
Delete the async from the function declaration
Avatar
boom done
Avatar
Should work now
If it does, we gonna set up the middleware
Avatar
yes it does 🙂
Avatar
Perfect. Now create a file called middleware.ts inside the same folder app is in.
Avatar
so root directory
Avatar
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
Avatar
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
Avatar
/dashboard* or what you said. Not entirely sure atm
I think in .env but not 100% sure. If NextAuth itself doesn't already throw warnings about it not finding it, it should work
Avatar
nah it loads it, which is nice
and i got NEXTAUTH_URL set too http://localhost:/3000 for now
Avatar
Avatar
yes
brain is fried for coding so much in the past week xD
Avatar
XD
Middleware is done?
Avatar
export { default } from 'next-auth/middleware'

export const config = { matcher: ['/dashboard'] }
Avatar
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 in
Avatar
yeh that's perfecty fine, once i make more pages i'll be securing it
Avatar
Alright then. Should be either /dashboard*, /dashboard(.*) or probably both work
Anyway. Did you test it?
Avatar
well, the page is still loading, just gotta enable it (i think)
Avatar
Enable it?
Avatar
oh fuck, register is breaking one sec
well, you can type, i just gotta figure out why this is broken so hard xD
Avatar
How is it broken?
Avatar
again :v
Image
Avatar
Stop importing options from your NextAuth config XD
You don't need it
Avatar
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'
Avatar
Ooooh, don't import bcrypt then
Avatar
I'm using bcrypt :v
i don't send naked passwords
Avatar
Yeah, but you don't wanna hash the password client side
Avatar
(╯°□°)╯︵ ┻━┻
Avatar
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)
Avatar
ahhh, alright well gimme a bit i gotta do my backend
Avatar
Sure
Avatar
there, i think i got it
Avatar
Test it so that you know
Avatar
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
Avatar
Change the schema and then sync it
Avatar
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
Avatar
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 date
Btw, 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-started
Avatar
my database is setup the way i wanna have it anyways.
Avatar
Yeah, but db push will change your database to fit your schema file
So in that case, make sure your schema file fits your database before you push
Avatar
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.
Avatar
Then your typing should be correct. Is your registration working again?
Avatar
bcrypt hash is being annoying :v
DUR i didn't await it
Avatar
Why?
XD
hashSync exists as well🤷‍♂️
Avatar
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
Avatar
well, dashboard doesn't throw people around. so not sure about that one.
Avatar
Can you show me your file structure?
Avatar
:blobsweats:
Image
Avatar
You have a src folder
And your app folder is inside there
Avatar
Yerp
that's how it was created
Avatar
So your middleware.ts has to go there as well
Avatar
Ah, so it's in the layout.ts location
Avatar
It has to be where the app (or pages) folder is
Basically
Wait, no
Outside the app folder
Inside the src folder
But not inside the app folder
It should be /src/middleware.ts
Avatar
alright it doesn't like the /dashboard* or /*
Avatar
Why not?
Avatar
just doesn't like it
Image
THERE it goes, throws to the login 🙂
Avatar
What did you change?
The matcher in the middleware is very trivial sadly
Avatar
it's in /src folder and it's just /dashboard
Avatar
I think it should actually be /dashboard/:path* then
Or /dashboard/(.*)
Same thing actually
Avatar
damn it, now when i register, it throws the error cause it loops and just does the loop for the check xD
Avatar
LOL, fix it
Avatar
im tryin, im seeing errors in my console saying it's connection local BUT it actually does the registration xD
Avatar
Those should be it btw to protect /dashboard and every subpage of it. * is zero or more
Can you show me the errors?
Avatar
Image
Avatar
Port 465?
Avatar
yeah not even using that xD
funny thing is, it creates the account THEN throws that
Avatar
Did you maybe add a wrong connection string or something somewhere in your code?
Maybe some leftovers from sequelize?
Avatar
OH 465 is my email service
Avatar
XD
Well, then it's fine
So we can move on?
Avatar
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
Avatar
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 returns
On a successful login ofc
Avatar
yea... i got nothing near that xD
Avatar
I copy and pasted that as well once I found it XD
And modified it a tiny bit to fit my needs
Avatar
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.
Avatar
Huh?
Avatar
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?
Image
Avatar
Interesting
404 or what?
Avatar
that was the redirect to that page
Avatar
Yeah, it's supposed to do that
Avatar
and if i go to the dashboard it won't let me in xD
Avatar
Can you reach the login?
You can't, right?
Avatar
res i can
Avatar
You can? What's this then?
Avatar
"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
Avatar
Oh
Well
You know
Avatar
that's a redirect to the login page of when i tried to login
Avatar
So when you login, what happens?
Avatar
just throws me back into the login page
Avatar
Do this first and then we'll see if it still persists and why: https://next-auth.js.org/configuration/pages
Avatar
i have
    pages: ({
        signIn: '/dashboard/login'
    })
already
Avatar
Alright then
So you login at /dashboard/login. And then it throws you to the same page again or to /api/auth/signin?
Avatar
http://localhost:3000/dashboard/login?callbackUrl=http%3A%2F%2Flocalhost%3A3000%2Fdashboard%2Flogin
Avatar
Oh, well then
Avatar
yerp.
Avatar
The callbackUrl is where it wants to redirect you after a successfull login. You're getting redirected to the login lol
But
I don't think that's the issue
This right here. What kind of error is it?
Avatar
It was website console
Avatar
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.logs inside your authorize function to check what exactly is happening
Avatar
well, my authorize it's got red squigly under it atm xD
Avatar
Show me
Avatar
Image
Avatar
Yes, that's because of that
Avatar
won't let me screen shot it, leaves too fast
ah fuck i probably forgot that
Avatar
Just add a // @ts-ignore above it
Avatar
would you like to do a video call
Avatar
Alright
Avatar
This was resolved in a DM video call Thank you!
Answer