How to have NextJS Auth example automatically login after sign-up - reactjs

The example in the NextJS repository uses cookie storage and iron-session in order to maintain auth during a user's logged in session. The example code is at this URL:
https://github.com/vercel/next.js/tree/canary/examples/with-passport
Unfortunately, it forces the user to first signup for a new account, then login with those credentials instead of automatically logging the user in when a successful signup is made.
It uses the NextJS API Routes, and the Passport Local strategy for authentication. I'm pasting the code below for both the sign up and the login routes.
I found some other SO posts that talked about how to use the authenticate method in login inside the signup method, but I believe that requires middleware that I don't understand. I can't just paste in the code from login into signup.
My question is, I want to have signup automatically create the cookie and then redirect to some other page in a logged in state.
/pages/api/signup.js
import { createUser } from '../../lib/user'
export default async function signup(req, res) {
try {
await createUser(req.body)
res.status(200).send({ done: true })
// WANTING TO ADD SOME CODE POSSIBLY HERE TO AUTO LOGIN
} catch (error) {
console.error(error)
res.status(500).end(error.message)
}
}
/pages/api/login.js
import passport from 'passport'
import nextConnect from 'next-connect'
import { localStrategy } from '../../lib/password-local'
import { setLoginSession } from '../../lib/auth'
const authenticate = (method, req, res) =>
new Promise((resolve, reject) => {
passport.authenticate(method, { session: false }, (error, token) => {
if (error) {
reject(error)
} else {
resolve(token)
}
})(req, res)
})
passport.use(localStrategy)
export default nextConnect()
.use(passport.initialize())
.post(async (req, res) => {
try {
const user = await authenticate("local", req, res);
// session is the payload to save in the token, it may contain basic info about the user
const session = { ...user };
await setLoginSession(res, session);
res.status(200).send({ done: true });
} catch (error) {
console.error(error);
res.status(401).send(error.message);
}
});

Related

How to make simple protected route using nextAuth?

I wanna make simple protected route.
I have credentials provider and nextAuth middleware. I just wanna make simple logic:
if user is logged in he can visit /profile, and if he visits /signup or /signin redirect him to /profile, and if he isnt logged he cant visit /profile and redirect him to /signin
some routes are neutral - for example he can visit /shop while being logged in or not.
there is my [...nextauth].ts
export default NextAuth({
session: {
strategy: 'jwt',
},
providers: [
CredentialsProvider({
type: 'credentials',
async authorize(credentails) {
const { password, email } = credentails as Signin
try {
const client = await connectToDatabase()
if (!client) return
const db = client.db()
const user = await existingUser(email, db)
if (!user) throw new Error('Invalid credentails!')
const isPasswordCorrect = await verifyPassword(password, user.password)
if (!isPasswordCorrect) throw new Error('Invalid credentails!')
return { email: user.email, name: user.name, id: user._id.toString() }
} catch (e: unknown) {
if (e instanceof Error) {
throw new Error(e.message)
}
}
},
}),
],
})
Apart from other answers what you can do is-
At component mount at signin and sign up check user is authenticated or not. If authenticated. use router.push to profile else be at signin/signup.
At profile again check for authentiction at component mount, if not auth push to signin else be at profile. Important thing here is don't show the layout, content of profile page before checking user is authenticated or not. Use a spiner or loader till auth check is going on.
write a middleware
const authorizedRoles = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return next(
// write logic to handle errors
new ErrorHandler(
`Role (${req.user.role}) is not allowed`,
403
)
);
}
next();
};
};
then whichever routes you want to protect, use this middleware. Then on protected pages' getServerSideProps
export async function getServerSideProps(context) {
const session = await getSession({ req: context.req });
if (!session || session.user.role !== "admin") {
return {
redirect: {
destination: "/home",
// permanent - if `true` will use the 308 status code which instructs clients/search engines to cache the redirect forever.
permanent: false,
},
};
}
return {
props: {},
};
}

How to request in server side for Credentials Provider in next-auth

I'm having trouble figuring out how to request in backend for a credentials provider login.
I created 2 Credential Provider with different ids(credentials and token)
I attempted the following(per https://next-auth.js.org/getting-started/rest-api#post-apiauthsigninprovider)
POST /api/auth/signin/credentials
POST /api/auth/signin/token
But it seems to return 404 Not Found
I also tried utilizing the route used by the default login form
POST /api/auth/callback/credentials
POST /api/auth/callback/token
But also a 404(also included a csrfToken)
Here is a code snippet
import { HOME_PAGE, LOGIN_PAGE } from '#app/constants/routes';
import { backendApi } from '#app/lib/api';
import { getCsrfToken, signIn } from 'next-auth/react';
export default async function handler(req, res) {
try {
const { accessToken} = req.query;
const csrfToken = await getCsrfToken();
await backendApi.post('/auth/signin/token', {
csrfToken,
accessToken,
});
res.redirect(307, HOME_PAGE);
} catch (error) {
res.redirect(307, LOGIN_PAGE);
}
}

next-auth 4 session returns null, next.js

This is code in api/auth/[...nextAuth].js
import NextAuth from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import User from "#/models/user";
import connectToDb from "#/config/dbConnection";
export default NextAuth({
session: {
strategy: "jwt",
},
providers: [
CredentialsProvider({
async authorize(credentials) {
connectToDb();
const { email, password } = credentials;
// check if email and passwird entered
if (!email || !password) {
throw new Error("please enter email or password");
}
const user = await User.findOne({
email,
// becasue in pre.save I excluded returning password in api response
}).select("+password");
if (!user) {
throw new Error("Invalid email or password");
}
// check if password is correct
// I added comparePassword to userSchema methods in mongoose
const isPasswordMatched = await user.comparePassword(password);
if (!isPasswordMatched) {
throw new Error("please enter email or password");
}
// creating a promise
return Promise.resolve(user);
},
}),
],
callbacks: {
// jwt callback is only called when token is created
jwt: async ({ token, user }) => {
// user is obj that we have received from authorize Promise.resolve(user)
user && (token.user = user);
// not this token has user property
return Promise.resolve(token);
},
// user arg here is actully token that returned from jwt.
session: async ({ session, token }) => {
// session callback is called whenever a session for that particular user is checked
console.log("user in ...next auth api", token);
session.user = token.user;
// since I get error, I return Promise.resolve(session)
return Promise.resolve(session);
},
},
});
then I write a middleware to get the session:
const isAuthenticatedUser = asyncErrors(async (req, res, next) => {
// when user logged in we create the session of user
// getSession gets auth-token from req.headers
const session = await getSession({ req });
// THIS RETURNS NULL
console.log("session", session);
if (!session) {
return next(new ErrorHandler("Login first to access this resource", 401));
}
req.user = session.user;
next();
});
export { isAuthenticatedUser };
I write api/me to check the user
import nc from "next-connect";
import connectToDb from "#/config/dbConnection";
import { currentUserProfile } from "#/controllers/authController";
import { isAuthenticatedUser } from "#/middlewares/auth";
import onError from "#/middlewares/errors";
const handler = nc({ onError });
connectToDb();
handler.use(isAuthenticatedUser).get(currentUserProfile);
export default handler;
When I visit http://localhost:3000/api/me, since session is logged as null, I get not authneticated, even though, in chrome debugging tools' Aplication tab next-auth credentials are present
Since https://next-auth.js.org/getting-started/upgrade-v4
Version 4 makes using the SessionProvider mandatory.
this is _app.js component
import { SessionProvider } from "next-auth/react"
export default function App({
Component,
pageProps: { session, ...pageProps },
}) {
return (
// `session` comes from `getServerSideProps` or `getInitialProps`.
// Avoids flickering/session loading on first load.
<SessionProvider session={session} refetchInterval={5 * 60}>
<Component {...pageProps} />
</SessionProvider>
)
}
This problem is happening due to not specifying e.preventDefault() on login button.
The working code should look like this :-
async function login(e) {
e.preventDefault(); //Add this to your code.
const getLoginStatus = await signIn("credentials", {
redirect: false,
username,
password,
})};

Persist auth state in react/react native for Firebase

I am using react native for an ios app and firebase for authentication. Every time I leave the app and come back, it asks for a login. I want to persist the firebase login but don't really know where to put it.
I know I need to put this in:
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.LOCAL)
I have the following signIn function that runs when the login button is pressed on the signInScreen:
const signIn = async () => {
setLoading(true);
try {
await firebase.signIn(email, password);
const uid = firebase.getCurrentUser().uid;
const userInfo = await firebase.getUserInfo(uid);
const emailArr = userInfo.email.split("#");
setUser({
username: emailArr[0],
email: userInfo.email,
uid,
isLoggedIn: true,
});
} catch (error) {
alert(error.message);
} finally {
isMounted.current && setLoading(false);
}
};
I have the following signIn stuff in my firebaseContext:
const Firebase = {
getCurrentUser: () => {
return firebase.auth().currentUser;
},
signIn: async (email, password) => {
return firebase.auth().signInWithEmailAndPassword(email, password);
},
getUserInfo: async (uid) => {
try {
const user = await db.collection("users").doc(uid).get();
if (user.exists) {
return user.data();
}
} catch (error) {
console.log("Error #getUserInfo", error);
}
},
logOut: async () => {
return firebase
.auth()
.signOut()
.then(() => {
return true;
})
.catch((error) => {
console.log("Error #logout", error);
});
},
};
Where do I put the persist code I listed above from the docs?
Thanks!
When do you check if someon is signed in or not?
From the code shown it looks like you check it manuelly by calling currentUser. You have to consider that the persistance of auth state is asynchronous. That means if you call currentUser on auth before the localy saved auth state is loaded you would get there null and thing that the user is not signed in.
To get the auth state Firebase recommend to use the onAuthStateChanges event listener. With that you can listen to auth state changes no matter if you logged in or the persistet auth state is loaded.
The usage is very simple:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
} else {
// No user is signed in.
}
});
That is the reson I asked where you check if someon is signed in or not. If I could see that code I could help you adopt it to use that event listener.

Make a redirect in NextJs using auth0

I am using auth0 as login feature. After login the user is sent here:
export default async function callback(req, res) {
try {
await auth0.handleCallback(req, res, {
onUserLoaded: async (req, res, session, state) => {
//make here redirect on 2 different pages depending by session
}
});
} catch (error) {
console.error(error);
res.status(error.status || 400).end(error.message);
}
}
I want to do a redirect depending by the session, but i can't figure out how.
Question: How to do a redirect and to achieve what i described? ex:
if (session === something) {
res.redirect('first-page')
} else {
res.redirect('second-page')
}

Resources