Next-auth session not created using email verification - sqlite

I'm creating a web app with nextjs next-auth and have a problem. When user clicks sign in button they are directed to the email form which is okay then when you enter email the email is sent successfully, but when you click sign in from the email you are redirected to the main page but still not signed in. IN the console i get this error:
[next-auth][error][session_error]
https://next-auth.js.org/errors#session_error TypeError: Cannot read property 'name' of undefined
at Object.session (/home/dennis/shopsms/node_modules/next-auth/dist/server/routes/session.js:87:24)
[next-auth][error][session_error]
https://next-auth.js.org/errors#session_error TypeError: Cannot read property 'name' of undefined
at Object.session (/home/dennis/shopsms/node_modules/next-auth/dist/server/routes/session.js:87:24)
[next-auth][error][session_error]
https://next-auth.js.org/errors#session_error TypeError: Cannot read property 'name' of undefined
at Object.session (/home/dennis/shopsms/node_modules/next-auth/dist/server/routes/session.js:87:24)
This is my nextauth.js
// pages/api/auth/[...nextauth].js
import NextAuth from 'next-auth';
import Providers from 'next-auth/providers'
const options = {
site: process.env.NEXTAUTH_URL,
providers: [
Providers.Email({
name:'userauth',
server: {
port: 465,
host: 'smtp.gmail.com',
secure: true,
auth: {
user: process.env.EMAIL_USERNAME,
pass: process.env.EMAIL_PASSWORD,
},
tls: {
rejectUnauthorized: false,
},
},
session: {
jwt: true,
// Seconds - How long until an idle session expires and is no longer valid.
maxAge: 30 * 24 * 60 * 60, // 30 days
},
from: process.env.EMAIL_FROM,
})
],
callbacks: {
session: async (session, user) => {
const { getUserByEmail } = await adapter.Default({}).getAdapter()
const { id } = await getUserByEmail(session.user.email)
session.user.id = id
return session
}
},
database: process.env.DATABASE_URL
}
export default (req, res) => NextAuth(req, res, options)
What could be wrong?

i think you need to specify the returned object like this
return {name: session.user.name, email: session.user.email}

on the page where you need to check if user is signed in
import { useSession } from 'next-auth/client'
const [ session, loading ] = useSession()
console.log(session)
or server side
import { getSession } from 'next-auth/client'
export default async (req, res) => {
const session = await getSession({ req })
/* ... */
res.end()
}
what do you get?

Related

How to get accessToeken in NextAuth?

I am trying to implement a traditional authentication setup with NextAuth. The backend is sending a response like the below after a successful login. The library looks excellent but I don't see where the raw accessToken is saved or any way to get this. I need to add it with every request header I send to the backend. How to deal with this kind of authentication? I would be pleased if you spent some of your time helping me in this regard.
Backend Response after login. FYI I can change the response if need
{
"statusCode": 200,
"data": {
"accesstoken": "eyJhbGcicCI6IkpXVCJ9.eyJlbWFpbCI6ImFzaWY.......",
"user": {
"name": "Participant",
"image": "https://i.pravatar.cc/150?img=4",
"email": "asif.saho#gmail.com"
}
}
}
This is what my [...nextauth].ts looks like.
import axios from 'axios';
import NextAuth, { Session, User } from 'next-auth';
import { JWT } from 'next-auth/jwt';
import CredentialsProvider from 'next-auth/providers/credentials';
import { AUTH_CONST } from '../../../constants/authConst';
import { log } from '../../../services/logger';
const providers = [
CredentialsProvider({
type: 'credentials',
id: 'credentials',
name: 'credentials',
credentials: {
email: { label: 'email', type: 'email' },
password: { label: 'password', type: 'password' },
},
authorize: async (_credentials, req) => {
const res: Response = await axios.post(AUTH_CONST.signInBeUrl, {
email: req.query!.email,
password: req.query!.password,
});
if (res.data.statusCode === 200) {
return res.data.data.user;
}
return null;
},
}),
];
const logger = {
error(code: any, metadata: any) {
log.error('next auth', JSON.stringify(code, null, 2));
log.error('next auth', JSON.stringify(metadata, null, 2));
},
warn(code: any) {
log.warn('next auth', JSON.stringify(code, null, 2));
},
debug(code: any, metadata: any) {
log.info('next auth', JSON.stringify({ code, metadata }, null, 2));
},
};
export default NextAuth({
secret: process.env.SECRET,
providers,
debug: process.env.AUTH_DEBUG === 'true',
logger,
pages: {
signIn: AUTH_CONST.signIn,
signOut: AUTH_CONST.signOut,
error: AUTH_CONST.error,
},
});
With NextAuth, you can use the session strategy as jwt. This is the default. The session is saved in a cookie and never persisted anywhere.
session: {
strategy: "jwt"
}
You can add an API handler that accepts the token and sets it to the cookie.
For example
export default function handler(req, res) {
const { token } = req.body;
res.setHeader('Set-Cookie', `token=${token}`);
res.json({});
}
The cookie is attached to all the requests sent to your backend.
// This is an example of how to read a JSON Web Token from an API route
import { getToken } from "next-auth/jwt"
export default async (req, res) => {
const token = await getToken({ req })
if (token) {
// Signed in
console.log("JSON Web Token", JSON.stringify(token, null, 2))
} else {
// Not Signed in
res.status(401)
}
res.end()
}
Example is taken from the NextAuth doc.

Mern Stack : Passport.js google auth not working in production (heroku and firebase)

My application works as expected in localhost but when I deploy express backend to heroku and react frontend to firebase the google authentication stop working.
In heroku logs , I get the user profile from google and I get user logged from serialize and deserialize functions but when I make a request to get current user I get undefined.
For some reason the server is not setting the cookies in the browser , I can't find any session cookie when I open the cookies in storage in the browser.
I tried every possible solution I found in the internet but the problem persists.
Edit
I moved my frontend to heroku and removed domain from cookie options , this time it worked but only in private mode , any idea about why its not working in normal mode?
here is the code:
server.js
import express from "express";
import mongoose from "mongoose";
import passport from "passport";
import "./passport.js";
import routerLogin from "./routes/auth.js";
import routerUser from "./routes/user.js";
import cookieSession from "cookie-session";
import cookieParser from "cookie-parser";
import { createRequire } from "module";
const require = createRequire(import.meta.url);
require("dotenv").config();
const bodyParser = require("body-parser");
const morgan = require("morgan");
import cors from "cors";
const app = express();
app.use(cookieParser());
app.use(
cors({
origin: "https://appname-339620.firebaseapp.com",
credentials: true,
methods: "GET,HEAD,PUT,PATCH,POST,DELETE",
})
);
app.use(bodyParser.json());
// db connection
mongoose
.connect(process.env.DATABASE, {
useNewUrlParser: true,
useUnifiedTopology: true,
})
.then(() => console.log("DB Connected"))
.catch((err) => console.log("DB Connection Error: ", err));
//middlewares
app.use(morgan("dev"));
app.use(express.json());
app.set("trust proxy", 1);
app.use(
cookieSession({
name: "SocialMedia-auth-session",
secret: process.env.SESSION_SECRET,
httpOnly: true,
secure: true,
sameSite: "none",
domain: "https://appname-339620.firebaseapp.com/",
})
);
app.use(passport.initialize());
app.use(passport.session());
// routes
app.use("/", routerLogin);
app.use("/", routerUser);
const port = process.env.PORT || 8000;
app.listen(port, () => console.log(`the app listening on port ${port}!`));
passport.js
import passport from "passport";
import User from "./models/user.js";
import Google from "passport-google-oauth20";
const GoogleStrategy = Google.Strategy;
import { createRequire } from "module";
const require = createRequire(import.meta.url);
require("dotenv").config();
passport.serializeUser((user, done) => {
console.log("user from serialize", user);
done(null, user.id);
});
passport.deserializeUser(async (id, done) => {
const user = await User.findById(id).populate("bookmarks");
console.log("user from deserialize", user);
done(null, user);
});
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_APP_ID,
clientSecret: process.env.GOOGLE_APP_SECRET_KEY,
callbackURL: "/auth/google/callback",
proxy: true,
},
async (accessToken, refreshToken, profile, done) => {
console.log("profile from google =>", profile);
const user = await User.findOne({ GoogleID: profile.id });
if (!user) {
const newUser = await User.create({
GoogleID: profile.id,
name: profile.displayName,
email: profile.emails[0].value,
picture: profile.photos[0].value,
});
if (newUser) {
done(null, newUser);
}
} else {
done(null, user);
}
}
)
);
callback route in auth.js
routerLogin.get(
"/auth/google/callback",
passport.authenticate("google", {
successRedirect: "https://appname-339620.firebaseapp.com",
failureRedirect: "https://appname-339620.firebaseapp.com/error",
})
);
current user Route
routerUser.get("/auth/user", async (req, res) => {
console.log("req.user =>", req.user); // undefined in production
res.send(req.user);
});
axios call to current user
useEffect(() => {
const ourRequest = axios.CancelToken.source();
const fetchUser = async () => {
try {
const res = await axios.get(
"/auth/user",
{
withCredentials: true,
},
{ cancelToken: ourRequest.token }
);
if (res.data) {
setUser({ user: res.data, loggedIn: true });
}
}
} catch (err) {
setUser({ ...state, userError: true });
showNotification({ // I get this error notification when I try to connect in production
color: "red",
message: "failed to connect ! ",
autoClose: 5000,
disallowClose: true,
icon: <AlertOctagon size={22} strokeWidth={1} color={"white"} />,
});
}
};
fetchUser();
return () => ourRequest.cancel;
}, []);
axios default base URL in app.js
axios.defaults.baseURL = "https://nameapp.herokuapp.com";
Authorized redirect URIs in google settings
https://nameapp.herokuapp.com/auth/google/callback
My mistake was I forgot to add https:// in the url bar in normal chrome tab , normal chrome tab doesn't accept cookies when the url is not secure (http) , only incognito mode accept cookies in http.
I also removed domain from cookie option(server.js) when I moved my react app to heroku.(but I think you should keep it when hosting to firebase)
Maybe it wasn't necessary to move from firebase, I believe I was making the same mistake when my frontend was hosted on firebase .. but I'm too lazy to check it now

How to add additional data to signIn Promise return in NEXT-AUTH?

This is how we are authorizing users in our website
signIn('credentials', {
phoneNumber: verifiedPhone,
code: otp.data,
type: 'phone',
redirect: false,
}).then((res) => {
console.log(res) // default response {error,status,ok,url}
// How can i add additional data to this response, in my case user session
// if (!res.user.name) openModal('add_name')
// else toast.success('You are all set!')
});
By default, signIn will then return a Promise, that resolves:
{
error: string | undefined
status: number
ok: boolean
url: string | null
}
And we wanna add custom data to this promise return.
Actually what we wanna do is to sign user in and if the user is new, he/she is supposed to have no username so a modal opens up, enters his/her username and we update the next-auth session.
[...nextauth].js:
...
async authorize(credentials, req) {
// check the code here
const res = await requests.auth.signInEnterOtp(
credentials.phoneNumber,
credentials.code,
credentials.type
);
if (!res.ok) return null
return {
user: {
access_token: res.data?.access_token,
token_type: res.data?.token_type,
expires_at: res.data?.expires_at,
user_info: {
id: res.data?.user.id,
name: res.data?.user.name,
phone: res.data?.user.phone,
user_type: res.data?.user.user_type,
},
},
};
},
...
I eventually figured it like this:
...
.then(async (res) => {
const session = await getSession()
...
})
...
Bu i have another problem, it is to update the session with new username (
EDIT
i found a way of how to change session after sign in
[...nextauth].js :
...
async authorize(credentials, req){
...
if(credentials.type === 'update_name'){
const session = await getSession({ req })
return session.user.name = credentails.name
}
...
}
on the client :
signIn('credentials', {
name: newName,
type: 'name_update',
redirect: false
)

How to implement credentials authorization in Next.js with next-auth?

I try to get credentials auth with next-auth, but I have no experience to use it and whatever I do, i get following message:
[next-auth][error][callback_credentials_jwt_error] Signin in with credentials is only supported if JSON Web Tokens are enabled
https://next-auth.js.org/errors#callback_credentials_jwt_error
This is my src/pages/api/auth/[...nextauth].js file.
import NextAuth from 'next-auth'
import Providers from 'next-auth/providers'
import User from "#models/User"
const options = {
NEXTAUTH_URL:process.env.NEXTAUTH_URL,
providers: [
Providers.Credentials({
// The name to display on the sign in form (e.g. 'Sign in with...')
name: 'Avista',
// The credentials is used to generate a suitable form on the sign in page.
// You can specify whatever fields you are expecting to be submitted.
// e.g. domain, username, password, 2FA token, etc.
credentials: {
email: {label: "Email", type: "text"},
password: {label: "Password", type: "password"}
},
authorize: async (credentials) => {
// Add logic here to look up the user from the credentials supplied
// const user = {id: 1, name: 'J Smith', email: 'jsmith#example.com'}
const user = await User.findOne()
console.log("Данные входа", credentials)
if (user) {
// Any object returned will be saved in `user` property of the JWT
return Promise.resolve(user)
} else {
// If you return null or false then the credentials will be rejected
return Promise.resolve(null)
// You can also Reject this callback with an Error or with a URL:
// return Promise.reject(new Error('error message')) // Redirect to error page
// return Promise.reject('/path/to/redirect') // Redirect to a URL
}
}
}),
Providers.Email({
server: {
host: process.env.EMAIL_SERVER_HOST,
port: process.env.EMAIL_SERVER_PORT,
auth: {
user: process.env.EMAIL_SERVER_USER,
pass: process.env.EMAIL_SERVER_PASSWORD
}
},
from: process.env.EMAIL_FROM
}),
],
// A database is optional, but required to persist accounts in a database
database: process.env.MONGO_URI,
secret: process.env.JWT_SIGNING_PRIVATE_KEY,
},
}
I don't know what I do wrong.
try adding this to your [...nextauth].js page
session: {
jwt: true,
// Seconds - How long until an idle session expires and is no longer valid.
maxAge: 30 * 24 * 60 * 60, // 30 days
},
read more about the options here: https://next-auth.js.org/configuration/options#jwt
This might be useful for someone else.
You need to specify that you want to use the jwt auth style.
Make options.session to be:
{ jwt: true }
If you using next-auth version 4, you should instead do:
{ strategy: 'jwt'}
Add this after providers: []
callbacks: {
jwt: async ({ token, user }) => {
user && (token.user = user)
return token;
},
session: async ({ session, token }) => {
session.user = token.user
return session;
}
},
secret: "secret",
jwt: {
secret: "ksdkfsldferSDFSDFSDf",
encryption: true,
},

NextAuth with custom Credential Provider Not creating session

I am attempting to implement NextAuth in my NextJs app. I am following the official documentation. But for one reason or the other, it seems like the user session object is not generated on login.
Here is my code from my pages/api/auth/[...nextauth].js file
import NextAuth from "next-auth";
import Providers from "next-auth/providers";
import axios from "axios";
export default (req, res) =>
NextAuth(req, res, {
providers: [
Providers.Credentials({
id: 'app-login',
name: APP
authorize: async (credentials) => {
console.log("credentials_:", credentials);
try {
const data = {
username: credentials.username,
password: credentials.password
}
// API call associated with authentification
// look up the user from the credentials supplied
const user = await login(data);
if (user) {
// Any object returned will be saved in `user` property of the JWT
return Promise.resolve(user);
}
} catch (error) {
if (error.response) {
console.log(error.response);
Promise.reject(new Error('Invalid Username and Password combination'));
}
}
},
}),
],
site: process.env.NEXTAUTH_URL || "http://localhost:3000",
session: {
// Use JSON Web Tokens for session instead of database sessions.
// This option can be used with or without a database for users/accounts.
// Note: `jwt` is automatically set to `true` if no database is specified.
jwt: true,
// Seconds - How long until an idle session expires and is no longer valid.
maxAge: 1 * 3 * 60 * 60, // 3 hrs
// Seconds - Throttle how frequently to write to database to extend a session.
// Use it to limit write operations. Set to 0 to always update the database.
// Note: This option is ignored if using JSON Web Tokens
updateAge: 24 * 60 * 60, // 24 hours
},
callbacks: {
// signIn: async (user, account, profile) => { return Promise.resolve(true) },
// redirect: async (url, baseUrl) => { return Promise.resolve(baseUrl) },
// session: async (session, user) => { return Promise.resolve(session) },
// jwt: async (token, user, account, profile, isNewUser) => { return Promise.resolve(token) }
},
pages: {
signIn: '/auth/credentials-signin',
signOut: '/auth/credentials-signin?logout=true',
error: '/auth/credentials-signin', // Error code passed in query string as ?error=
newUser:'/'
},
debug: process.env.NODE_ENV === "development",
secret: process.env.NEXT_PUBLIC_AUTH_SECRET,
jwt: {
secret: process.env.NEXT_PUBLIC_JWT_SECRET,
}
});
const login = async data => {
var config = {
headers: {
'Content-Type': "application/json; charset=utf-8",
'corsOrigin': '*',
"Access-Control-Allow-Origin": "*"
}
};
const url = remote_user_url;
const result = await axios.post(url, data, config);
console.log('result', result);
return result;
};
What am I not getting it right here? Thanks for the help.
I managed to resolve the issue eventually. Something was wrong due to specifying the 'id' and 'name' options for the custom credential provider
I have removed them and the code is working now.

Resources