I am working on a Next.js project, in which I have included a login system with NextAuth.
Everything was working fine at the beginning, but recently I keep getting an error every time I try to get the session.
The Error:
https://pastebin.com/Mh624N3c
StackOverflow doesn't let me post the whole error, so I had to use Pastebin.
This is the first time I encounter such an error, and I can't seem to be able to find a solution. I am using JWT as the session strategy, if that has to do anything with the issue.
This is the code I use for handling the authentication & session:
await NextAuth(req, res, {
adapter: MongoDBAdapter(clientPromise),
pages: {
signIn: "/login"
},
providers: [
CredentialsProvider({
name: "credentials",
credentials: {
email: { label: "Email", type: "email", placeholder: "example#email.com" },
password: { label: "Password", type: "password" }
},
async authorize(credentials, req) {
const account = await Accounts.exists(credentials.email)
const cryptr = new Cryptr(Config.secret)
const encEmail = cryptr.encrypt(credentials.email)
const url = process.env.NODE_ENV == "development" ? "http://localhost:3000/" : Config.url
if (account) {
const password = cryptr.decrypt(account.password)
if (credentials.password == password) {
return {
...account,
_id: null
}
} else {
return res.redirect("/login?error=true")
}
} else {
const code = await Accounts.requestVerification(credentials.email, password)
const message = {
to: credentials.email,
from: "noreply#bytestobits.dev",
subject: "BytesToBits API - Create Your Account",
html: emailStyle(credentials?.email, url, code),
}
SendGrid.send(message).then(() => console.log("Email Sent"))
return res.redirect("/verify?email=" + encEmail)
}
}
})
],
jwt: {
secret: Config.secret,
encryption: true
},
secret: Config.secret,
session: {
strategy: "jwt"
},
callbacks: {
async jwt({ token, user }) {
if (user) {
token.user = user
}
return token
},
async session({ session, token }) {
let data = token.user
if (data) {
if (await Accounts.exists(data.email)) {
data.token = await Accounts.getToken(data.email)
}
data.tokenInfo = await Accounts.tokenInfo(data.token)
}
return data
}
}
})
This happens every time I try to fetch the session or authenticate.
When the user authenticates, a session must be formed, which can be fetched from the client for usage. However, whenever I try to authenticate of fetch the session, a "Parse Error: Header overflow" occurs.
I managed to fix the issue! Turns out the Session object was way too long and caused this error.
Basically in the data's tokenInfo field, it had a really long array. So removing that specific field fixed the issue!
Related
The bounty expires in 7 days. Answers to this question are eligible for a +100 reputation bounty.
AG_HIHI is looking for an answer from a reputable source.
I have successfully implemented authentication using gmail account in my app.
The problem is when the user signs-in again, the browser automatically picks the previous account which breaks the flow as I will explain below. p
Based on my research, adding prompt: "select_account" here should have solved the issue. But, it had no effect.
router.get(
"/auth/google",
passport.authenticate("google", {
scope: [
"email",
"profile",
],
prompt: "select_account",
})
);
Here's how automatically picking the user account that was previously used to sign-in breaks the sign-in if the user tries to sign-in again.
This is how the sign-in works:
STEP 1:
This endpoint is called from the frontend:
router.get(
"/auth/google",
passport.authenticate("google", {
scope: [
"email",
"profile",
],
prompt: "select_account",
})
);
STEP 2:
After, the user picks an account, he is redirected to this callback endpoint:
router.get(
"/auth/google/callback",
passport.authenticate("google", {
failureRedirect: baseFrontendUrl,
session: false,
}),
function (req, res) {
User.findOne({ _id: req.user._id })
.then((user) => {
const payload = {
id: req.user._id,
};
console.log("🚀 ~ file: users.js:178 ~ .then ~ payload", payload);
jwt.sign(
payload,
keys.SecretKey,
{ expiresIn: 3600 * 24 * 356 },
(error, token) => {
if (error) {
res.status(400).json({
message: "Error logging in user.",
});
} else {
const redirect_url = `${baseFrontendUrl}/OAuthRedirecting?token=${token}`;
res.redirect(redirect_url);
}
}
);
})
.catch((error) => {
res.status(500).json({
message: "An error occured authenticating user using google.",
});
});
}
);
The problem is that if the user does not pick an account, he does not get redirected to that endpoint. So the second sign-in fails.
A solution to this could be to force the user to pick an account every time he signs-in but I couldn't find a way to do this.
This is how the google passport strategy is implemented:
passport.use(
new GoogleStrategy(googe_passport_config, function (
request,
accessToken,
refreshToken,
google_profile,
done
) {
let name = !!google_profile._json.given_name
? google_profile.given_name
: "Name";
let surname = !!google_profile._json.family_name
? google_profile.family_name
: "Surname";
let email = !!google_profile._json.email ? google_profile.email : "";
User.findOne({ email: google_profile._json.email })
.then((user) => {
if (!!user) {
return done(null, user);
} else {
userServices
.registerUserThroughGoogleAuth(
name,
surname,
email,
google_profile.id
)
.then((created_user) => {
if (!!created_user) {
return done(null, created_user);
}
})
.catch((error) => {
const error_to_be_returned = new Error("Error creating user");
return done(error_to_be_returned, null);
});
}
})
.catch((error) => {
const error_to_be_returned = new Error("Error finding user");
return done(error_to_be_returned, null);
});
})
);
I added some console logs there and nothing gets logged the second time the user tries to sign-in. So it's not even getting called.
I'm using mongodb and next.js to check if a user exists in a database array, then add a like and increment a database element if this is true. The database is structured liked this:
likes -> by -> [array of users]
My code to find if a user exists in the database array is this:
db.collection('quotes').findOne({
likes: {
by: cookies.get('user')
}
}, async function(liked) {
if (!liked) {
//...Add like
However, for some reason the like just keeps getting added, regardless of the check if the user exists. I also get this error for some reason:
API resolved without sending a response for /api/like, this may result in stalled requests.
Here's my full code for api/like
import clientPromise from "../../lib/mongodb";
const ObjectId = require('mongodb').ObjectId;
import Cookies from 'cookies'
export default async function handler(req, res) {
switch (req.method) {
case 'POST': {
return like(req, res);
}
}
}
async function like(req, res) {
const cookies = new Cookies(req, res)
const client = await clientPromise;
const db = client.db("the-quotes-place");
if (cookies.get('user') != null) {
db.collection('quotes').findOne({
likes: {
by: cookies.get('user')
}
}, async function(liked) {
if (!liked) {
await db.collection('quotes').updateOne(
{ _id: new ObjectId(req.body.id) },
{
$inc: {
"likes.num": 1
},
$push: {
"likes.by": cookies.get('user')
}
})
res.status(200).json({
message: 'Succesfully added like',
success: true
})
}
})
} else {
res.status(403).json({
message: 'Error: Cookies not found',
success: true
})
}
}
What's going on here?
Thanks for any help.
When i try to login using Talend API Tester, getting this into terminal:
API resolved without sending a response for /api/auth/callback/credentials, this may result in stalled requests.
Besides, here is the image of Request:
I followed this: Next-Auth.js -> Rest API
Why I'm getting this kind of response & warning?
Below is the [..nextauth.js] file's code. What's wrong with my code?
import NextAuth from 'next-auth'
import Providers from 'next-auth/providers'
import prisma from '../../../lib/prisma'
const options = {
providers: [
Providers.Credentials({
name: 'Credentials',
credentials: {
email: { label: "Email", type: "email", placeholder: "something#example.com" },
password: { label: "Password", type: "password" }
},
async authorize(credentials) {
const {email, password} = credentials
const user = await prisma.user.findFirst({ where: { email, password } })
console.log(user);
// If no error and we have user data, return it
if (user) {
return user
}
// Return null if user data could not be retrieved
return null
}
})
]
}
export default async function handle(req, res) {
console.log('I see');
NextAuth(req, res, options)
}
Note: Actually I don't want to send csrfToken in the body. Only email & password to the endpoint. Please have a look into my this question How to do authentication using NextAuth.js without login page (using postman)
I'm integrating next-auth package to my fresh Next.js project. I have followed all of the Next.js and next-auth documentations but not able to find a solution.
The issue I'm facing goes like this:
I want to Login to my Next.js app using Email & Password submitted to my API Server running on Laravel.
When submitting the login form I'm executing the below function.
import { signIn } from "next-auth/client";
const loginHandler = async (event) => {
event.preventDefault();
const enteredEmail = emailInputRef.current.value;
const enteredPassword = passwordInputRef.current.value;
const result = await signIn("credentials", {
redirect: false,
email: enteredEmail,
password: enteredPassword,
});
console.log("finished signIn call");
console.log(result);
};
And code shown below is in my pages/api/auth/[...nextauth].js
import axios from "axios";
import NextAuth from "next-auth";
import Providers from "next-auth/providers";
export default NextAuth({
session: {
jwt: true,
},
providers: [
Providers.Credentials({
async authorize(credentials) {
axios
.post("MY_LOGIN_API", {
email: credentials.email,
password: credentials.password,
})
.then(function (response) {
console.log(response);
return true;
})
.catch(function (error) {
console.log(error);
throw new Error('I will handle this later!');
});
},
}),
],
});
But when try to login with correct/incorrect credentials, I get the below error in Google Chrome console log.
POST http://localhost:3000/api/auth/callback/credentials? 401 (Unauthorized)
{error: "CredentialsSignin", status: 401, ok: false, url: null}
Am I missing something here?
From the documentation (https://next-auth.js.org/providers/credentials#example)
async authorize(credentials, req) {
// Add logic here to look up the user from the credentials supplied
const user = { id: 1, name: 'J Smith', email: 'jsmith#example.com' }
if (user) {
// Any object returned will be saved in `user` property of the JWT
return user
} else {
// If you return null or false then the credentials will be rejected
return null
// You can also Reject this callback with an Error or with a URL:
// throw new Error('error message') // Redirect to error page
// throw '/path/to/redirect' // Redirect to a URL
}
}
You are not currently returning a user or null from the authorize callback.
Answer posted by shanewwarren is correct, but here is more elaborated answer,
Using axios to solve this
async authorize(credentials, req) {
return axios
.post(`${process.env.NEXT_PUBLIC_STRAPI_API}/auth/login`, {
identifier: credentials.identifier,
password: credentials.password,
})
.then((response) => {
return response.data;
})
.catch((error) => {
console.log(error.response);
throw new Error(error.response.data.message);
}) || null;
},
Before the server starts, all plugins etc are registered. I create my strategy and set JWT as default authentication method for the server.
await server.register(require('hapi-auth-jwt2'));
await server.register(require('~/utils/jwt-key-signer'));
server.auth.strategy(
'jwt', 'jwt',
{ key: process.env.API_KEY,
validate: true, // Temporarily using true
verifyOptions: { algorithms: [ 'HS256' ] }
});
server.auth.default('jwt');
Here's my route. I pass the payload from the handler request into a plugin that signs my key and returns a token:
'use strict';
const Boom = require('boom');
exports.plugin = {
name: 'user-create',
register: (server, options) => {
server.route({
method: 'POST',
path: '/user/create',
options: { auth: 'jwt' },
handler: async (request, h) => {
const { payload } = await request;
const { JWTKeySigner } = await server.plugins;
const token = await JWTKeySigner.signKeyReturnToken(payload);
const cookie_options = {
ttl: 365 * 24 * 60 * 60 * 1000, // expires a year from today
encoding: 'none', // we already used JWT to encode
isSecure: true, // warm & fuzzy feelings
isHttpOnly: true, // prevent client alteration
clearInvalid: false, // remove invalid cookies
strictHeader: true // don't allow violations of RFC 6265
}
return h.response({text: 'You have been authenticated!'}).header("Authorization", token).state("token", token, cookie_options);
},
options: {
description: 'Creates a user with an email and password',
notes: 'Returns created user',
tags: ['api']
}
});
}
};
Here's how I sign my key:
const jwt = require('jsonwebtoken');
exports.plugin = {
name: 'JWTKeySigner',
register: (server, options) => {
server.expose('signKeyReturnToken', async (payload) => {
jwt.sign(payload, process.env.API_KEY, { algorithm: 'HS256' }, async (err, token) => {
if (err) {
console.log(err)
}
await console.log(`TOKEN${token}`);
return token;
});
})
}
};
I then visit my route from Postman, then pass my user which contains an email address and password back to the round as JSON, and this is the response I get:
{
"statusCode": 401,
"error": "Unauthorized",
"message": "Missing authentication"
}
Ok, so that proves my route is successfully being protected. I now proceed to add my token into Postman:
Then I get this error:
{
"statusCode": 500,
"error": "Internal Server Error",
"message": "An internal server error occurred"
}
If I remove the token from postman, I get the "unauthorised" error.
All I'm trying to do is block outside access to my API, and only allow people who have permission to access it. This would be regular users who signup.
When I paste my token into JWT.io, I can see my data on the right hand side of the page, but JWT tells me it's an invalid signature.
I'd really appreciate some clarity here. I'm using hapi-auth-jwt2.
Thanks in advance
Hmm, I was writing another message to you but then I checked hapi-auth-jwt2 docs for validate options and it says;
validate - (required) the function which is run once the Token has been decoded with signature
async function(decoded, request, h) where:
decoded - (required) is the decoded and verified JWT received in the request
request - (required) is the original request received from the client
h - (required) the response toolkit.
Returns an object { isValid, credentials, response } where:
isValid - true if the JWT was valid, otherwise false.
credentials - (optional) alternative credentials to be set instead of decoded.
response - (optional) If provided will be used immediately as a takeover response.
Just try
server.auth.strategy(
'jwt', 'jwt',
{ key: process.env.API_KEY,
validate: validate: () => ({isValid: true}), // Temporarily using true
verifyOptions: { algorithms: [ 'HS256' ] }
});
Then let's see if 500 error continues.
Maybe some other thing in your code that throws an error. Did you enable debug on your server setup? You should see the details of that 500 error in your server console.