I am working on a project with NextAuth and after the user creation I need to create and let the user configure the account with Stripe Connect, to achieve this I need the user to be redirected to a certain page only when the user is created (as the documentation of Stripe says here), how can I achieve this? I was thinking about using the createUser callback in the NextAuth options but it does not seem to be the correct approach.
The following is my NextAuth options:
const options = {
secret: process.env.NEXTAUTH_SECRET,
adapter: PrismaAdapter(prisma),
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
DiscordProvider({
clientId: process.env.DISCORD_CLIENT_ID,
clientSecret: process.env.DISCORD_CLIENT_SECRET
}),
TwitchProvider({
clientId: process.env.TWITCH_CLIENT_ID,
clientSecret: process.env.TWITCH_CLIENT_SECRET
})
],
pages: {
signIn: '/signin'
},
callbacks: {
async signIn({ user, account, profile, email, credentials }) {
console.log("USER SIGNED IN, ", user, account);
// const user = await prisma.user.findUnique({})
// const account = await stripe.accounts.retrieve();
return true;
},
async redirect({ url, baseUrl }) {
return baseUrl;
},
async session({ session, user, token }) {
return { ...session, ...user };
},
async jwt({ token, user, account, profile, isNewUser }) {
return token;
}
},
events: {
createUser: async ({ user }) => {
console.log("USER CREATED, ", user);
const customer = await stripe.customers.create({ email: user.email });
const account = await stripe.accounts.create({ type: "standard" });
const accountLink = await stripe.accountLinks.create({
account: account.id,
refresh_url: process.env.PUBLIC_URL,
return_url: process.env.PUBLIC_URL,
type: 'account_onboarding',
});
await prisma.user.update({
where: { id: user.id },
data: {
customerId: customer.id,
stripeAccountId: account.id
},
});
}
},
cookies: {
sessionToken: {
name: 'next-auth.session-token',
options: {
httpOnly: true,
sameSite: 'lax',
path: '/',
secure: process.env.NODE_ENV === 'production',
domain: hostName
},
},
}
}
You can use the callbackUrl in the signIn method
https://next-auth.js.org/getting-started/client#specifying-a-callbackurl
the user will get redirected there when he first logs in
if the user is logging in for the first time it will first create the user and then redirect the new user to the specified URL in the callbackUrl.
Related
I am using next auth to authenticate users through credentials stored in a database. A JWT is returned and should contain common data such as their first and last name.
My […nextauth].ts file is as follows:
import Credentials from 'next-auth/providers/credentials';
import { verifyPassword } from '../../../lib/auth';
import { connectToDatabase } from '../../../lib/db';
export default NextAuth({
session: {
strategy: 'jwt',
},
providers: [
Credentials({
async authorize(credentials: Record<string, string> | undefined) {
if (credentials == undefined) {
throw new Error('Credentials not defined.');
}
const client = await connectToDatabase();
const usersCollection = client
.db(process.env.DB_DB)
.collection('account');
const user = await usersCollection.findOne({
email: credentials.email,
});
if (!user) {
client.close();
throw new Error('No user found.');
}
const isValid = await verifyPassword(
credentials.password,
user.password
);
if (!isValid) {
throw new Error('Your password is incorrect.');
}
client.close();
return {
user: {
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
},
};
},
credentials: {},
}),
],
pages: {
signIn: '/',
},
});
The session I receive from getSession is
expires: "2022-10-05T18:57:13.557Z"
user:
[[Prototype]]: Object
[[Prototype]]: Object
The authentication works as intended, throwing errors where credentials don’t match and returning the JWT where the details are correct.
I have made sure the user is being returned from the database and is being stored in the user variable. It just doesn’t seem to return anything inside.
You have to return Promise
const returnedUser={
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
},
return Promise.resolve(returnedUser);
The below code is code from [...nextauth].js .
The Goal is to achieve is to send POST request to save data and to set a session token with the returned result when using google-authentication.
To explain the code written: I am using next-auth's credential and google providers. In the Credential provider I am making a POST request to check for the user in the database hosted on localhost:8080. The credentials passed as parameters include email and password.
For Google Provider, I have kept the code default from the doc.
callbacks are there to save tokens.
import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google";
import CredentialsProvider from "next-auth/providers/credentials";
export default NextAuth({
// Configure one or more authentication providers
providers: [
CredentialsProvider({
async authorize(credentials){
//check if crenditials.email is present in database
const res =await fetch('http://localhost:8080/user/login?deviceToken=eEiLMMkzR1ypiCwp068z97:APA91bEiBpfwCmpZ5-ijVU4FKcl-4d0QkuWrBtXgcZRJF06MUw8GJvcBn_4ci-v1IFOD8wMF0bNqEheFq0LR0Vz5hXIktT-7sMwOfR52ULhy14NgjiUUW_0nNs5gBXAZHwhtifJluS7v', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(credentials),
})
const x=await res.json();
// console.log(x);
const user={email:x.user.email,name:`${x.user.firstName} ${x.user.lastName}`};
if(res.ok && user){
console.log("logged In");
return user;
}
console.log("error1");
return null;
}}),
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
authorization: {
params: {
prompt: "consent",
access_type: "offline",
response_type: "code"
}
}
}),
],
jwt: {
encryption:true,
},
callbacks:{
async jwt(token,account)
{
console.log(account);
if(account){
token.accessToken = account.accessToken;
}
return token;
},
}
})
You could use the signIn callback, however I'm not sure if this would be the purpose of this callback, it's what I'm doing at the moment.
This function gets called after the sign in process so you have access to the user's data.
async signIn({ account, profile }) {
if (account.provider === "google") {
// we can do DB queries here
console.log({
verified: profile.email_verified,
name: profile.given_name,
email: profile.email,
lastName: profile.family_name
})
return true
}
return true // do other things for other providers
}
UPDATE
It works now. All I had to change is
session: {
jwt: true,
strategy: "jwt"
}
I am new in next-auth. I was practicing next-auth sign-in with credentials using MongoDB as a database. Whenever I sign in with credentials, useSession doesn't return data as expected but if sign in with other providers like google and GitHub it returns data as expected. Below I have given my code.
[...nextauth.js] code:-
export default NextAuth({
// Configure one or more authentication providers
adapter: MongoDBAdapter(clientPromise),
session: {
jwt: true,
},
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
}),
CredentialsProvider({
async authorize(credentials) {
const client = await connect();
const usersCollection = client.db().collection("users");
const user = await usersCollection.findOne({
email: credentials.email,
});
if (!user) {
client.close();
throw new Error("No user found!");
}
const isValid = bcrypt.compare(credentials.password, user.password);
if (!isValid) {
client.close();
throw new Error("Invalid password/email");
}
client.close();
return user;
},
}),
// ...add more providers here
],
callbacks: {
async session({ session, user, token }) {
session.user = token.user;
console.log(user)
return session
},
async jwt({ token, user }) {
if (user) {
token.user = user;
}
// console.log(token)
return token
},
},
});
signIn logic:
const handleSubmit = async (e) => {
e.preventDefault();
const status = await signIn("credentials", {
redirect: false,
email: emailRef.current.value,
password: passwordRef.current.value,
});
console.log(status); // {error: null, status: 200, ok: true, url: 'http://localhost:3000/login'}
console.log(session); // {data: null, status: 'unauthenticated'}
};
Sorry for my bad English.
Thanks in advance. :)
How to return API errors to Login Component in NextAuth.js.Actually, I am trying to pass the Errors back to Login Component in NextAuth(Credentials Provider). I am getting this object in console error: "CredentialsSignin" ok: false status: 401 url: null [[Prototype]]: Object Everything is working fine like I am able to log in, but when I am trying to handle errors coming from APIs, I am unable to handle them.
[...nextauth.js] File
export default (req, res) =>
NextAuth(req, res, {
providers: [
CredentialsProvider({
authorize: async (credentials) => {
try {
const data = {
email: credentials.email,
password: credentials.password
}
const user = await login(data);
console.log("401 Error",user.data);
if (user.data.status==200) {
console.log("200 data",user.data);
return Promise.resolve(user.data);
}else if(user.data.status==401){
// Here I wants to Handle Errors and return them back to My login Compenent
}
} catch (error) {
if (error.response) {
console.log(error.response);
Promise.reject(new Error('Invalid Username and Password combination'));
}
}
},
}),
GoogleProvider({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
}),
],
pages: {
signIn: '/login',
},
callbacks: {
jwt: async ({token,user})=>{
if(user){
token.userid = user.id;
token.name = user.username;
token.token = user.token;
}
return token;
},
session: (session,token)=>{
return session;
}
},
secret:"test",
jwt:{
secret:"test",
encryption:true,
},
site: process.env.NEXTAUTH_URL || "http://localhost:3000",
session: {
jwt: true,
maxAge: 1 * 3 * 60 * 60, // 3 hrs
updateAge: 24 * 60 * 60, // 24 hours
}
});
const login = async data => {
var config = {
headers: {
'Content-Type': "application/json; charset=utf-8",
'corsOrigin': '*',
"Access-Control-Allow-Origin": "*"
}
};
const url = 'api/auth/login';
const result = await axios.post(url,data,config);
return result;
};
Login Components
const LoginSubmit = async (event) => {
event.preventDefault();
const enteredEmail = inputText.email;
const enteredPassword = inputText.password;
// console.log(enteredEmail);
const result = await signIn("credentials", {
redirect: false,
email: enteredEmail,
password: enteredPassword,
});
console.log("Final Result",result);
};
Yes you can do it :
Try :
// component.js
const res = await signIn("credentials", {
email: inputs.email,
password: inputs.password,
callbackUrl: `/`,
redirect: false,
});
if (res?.error) {
setLoading(false);
Swal.fire("Invalid Login", "username or password is incorrect");
}
and in Nextauth file you must throw an error if a user entred invalid credential!
//[...nextauth].js
const providers = [
Providers.Credentials({
name: "credentials",
authorize: async (credentials) => {
//statr try
try {
const user = await
axios.post(`${API_URL}/auth/local`, {
identifier: credentials.email,
password: credentials.password,
});
if (user) {
return { status: "success",
data:user.data };
}
} catch (e) {
throw new Error("Not found ");
}
},
}),
];
import Cookies from 'universal-cookie';
const cookies = new Cookies();
cookies.remove('abc');
console.log(cookies.getAll());
It is still printing my abc cookie.
May be you need to do something like
cookies.remove('abc', { path: '/' });
More info here
Cookies need to have both path and domain appended to them to be removed. Try this:
cookies.remove("abc", {path: "/", domain: ".example.com"})
If you are setting the cookie on a response in a login route/controller in express backend for JWT and are using 'httpOnly' option, you are unable to access the token from the client/react, even when using a third party library like 'universal-cookie' or 'document.cookie'.
You will need to clear the cookie on the response from the backend e.g. when a user logs out in the logout controller as detailed below.
Front-end:
// React redux logout action
export const logout = () => async (dispatch) => {
try {
await axios.get('/api/auth/logout')
localStorage.removeItem('userInfo')
dispatch({ type: type.USER_LOGOUT })
} catch (error) {
console.log(error)
}
}
Backend:
const User = require('../../models/userModel')
const generateToken = require('../../utils/generateToken')
// #desc Auth user & get token
// #route POST /api/auth/login
// #access Public
const login = async (req, res) => {
const { email, password } = req.body
try {
const user = await User.findOne({ email })
if (user && (await user.verifyPassword(password))) {
let token = generateToken(user._id)
res.cookie('token', token, {
maxAge: 7200000, // 2 hours
secure: false, // set to true if your using https
httpOnly: true,
})
res.json({
_id: user._id,
name: user.name,
email: user.email,
isAdmin: user.isAdmin,
token: token,
})
} else {
res
.status(401)
.json({ success: false, message: 'Invalid email or password' })
}
} catch (error) {
res.status(500).json({ success: false, message: error.toString() })
}
}
// #desc Logout controller to clear cookie and token
// #route GET /api/auth/login
// #access Private
const logout = async (req, res) => {
// Set token to none and expire after 1 seconds
res.cookie('token', 'none', {
expires: new Date(Date.now() + 1 * 1000),
httpOnly: true,
})
res
.status(200)
.json({ success: true, message: 'User logged out successfully' })
}
module.exports = {
login,
logout,
}
I just add this for people who may have similar problem in future, just like I had today. This may be an issue with asynchronous actions. Setting, removing cookies is asynchronous.