Make a redirect in NextJs using auth0 - reactjs

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')
}

Related

Why is my Azure redirect running twice and not stopping the fuction

I want to add the functionality for admins to disable end users access if necessary. It works just fine with non-SSO users. The check will prevent the user from logging in and show them a 'user is not active error'. When a non-active user tries to use Azure SSO to log in, the Azure SSO is still successful and displaying a spinner because there is not an active user. It should not allow them to 'log in' and redirect them to the home page with a displayed error that says 'user is not active'
Here is the function to change the user's isActive status on the backend
const changeUserStatus = asyncHandler(async (req, res) => {
const currentUser = await User.findById(req.user._id);
if (!currentUser) {
res.status(401);
throw new Error('User not found');
}
const user = await User.findByIdAndUpdate(req.params.id, req.body, {
new: true,
});
console.log(user);
res.status(201).json(user);
});
From the backend as well, here is the check for a user's isActive status in the normal login function
//check isActive status
if (user.isActive === false) {
res.status(400);
throw new Error('Not an active user');
}
Here is the check in the Azure SSO log in
if (!user.isActive) {
errors.azure = 'User is no longer permitted to access this application';
res.status(400);
throw new Error(errors.azure);
// console.log(errors);
// return res.status(401).json(errors);
}
Here is my authService.js
// Login user
const login = async (userData) => {
const response = await axios.post(API_URL + 'login', userData);
if (response.data) {
localStorage.setItem('user', JSON.stringify(response.data));
}
return response.data;
};
const azureLogin = async () => {
const response = await axios.get(API_URL + 'az-login');
return response.data;
};
Here is my authSlice
// Login user
export const login = createAsyncThunk('auth/login', async (user, thunkAPI) => {
try {
return await authService.login(user);
} catch (error) {
return thunkAPI.rejectWithValue(extractErrorMessage(error));
}
});
// Login user using AAD - this action sends the user to the AAD login page
export const azureLogin = createAsyncThunk(
'users/azureLogin',
async (thunkAPI) => {
try {
return await authService.azureLogin();
} catch (error) {
return thunkAPI.rejectWithValue(extractErrorMessage(error));
}
}
);
// Login user using AAD - this action redirects the user from the AAD login page
// back to the app with a code
export const azureRedirect = createAsyncThunk(
'users/azureRedirect',
async (code, thunkAPI) => {
try {
return await authService.azureRedirect(code);
} catch (error) {
return thunkAPI.rejectWithValue(extractErrorMessage(error));
}
}
);
And here is the AzureRedirect.jsx component. This is the component that receives the flow from the Microsoft/AAD login page. It is the re-entry point of the application, so to speak.
useEffect(() => {
const code = {
code: new URLSearchParams(window.location.search).get('code'),
};
if (user) {
toast.success(`Logged in as ${user.firstName} ${user.lastName}`);
navigate('/');
} else if (code) {
// This CANNOT run more than once
const error = dispatch(azureRedirect(code));
console.log(error);
} else {
console.log('No code found in URL');
}
}, [dispatch, navigate, user]);
if (!user) {
displayedOutput = <Spinner />;
} else {
displayedOutput = (
<div>
An error has been encountered, please contact your administrator.
<br />
<Link to='/login'>Return to Login</Link>
</div>
);
}
return <div className='pt-4'>{displayedOutput}</div>;

Having to press log out twice to actually destory my user's session - react + express

I've got a react front end that performs some actions. The relevant axios requests look like so:
const login = async () => {
await Axios.post('http://localhost:8000/login', {
username: username,
password: password,
}).then((response) => {
console.log("login response: ", response);
window.location.href = "http://localhost:3000/";
}).catch(err => {
alert(err.response.data);
});
};
// run on first render to see if user session is still active - remove console log later
useEffect(() => {
Axios.get("http://localhost:8000/isLoggedIn").then((response) => {
console.log("isLoggedIn resonse: ", response);
if (response.data.loggedIn === true) {
setLoginStatus(`Logged in as ${response.data.user}`);
}
})
}, [])
const Logout = async () => {
try {
await Axios.get('http://localhost:8000/logout').then((response) => {
console.log(response);
window.location.href = "http://localhost:3000/login";
}).catch(err => {
alert(err);
});
} catch (error) {
alert(error)
}
};
I keep having to press log out twice to actually log my user out. The logout route runs before the "isLoggedIn" route, according to my network tab. And it's successful, too. Here are the isLoggedIn and logout routes in my express backend:
export function isLoggedIn( req: any, res: any) {
if (req.session.user) {
// if our session already has a user, send true to the frontend
// frontend runs this get login on first render, so will have user data if cookie has not expired.
res.send({loggedIn: true, user: req.session.user})
} else {
res.send({loggedIn: false});
}
}
export function logout(req: any, res: any) {
if (req.session) {
req.session.destroy( (err: any) => {
if (err) {
res.status(400).send('Unable to log out');
} else {
res.send("Logout successful");
}
});
} else {
res.end();
}
}
I'm getting a successful logout. I just cannot figure out why I need to hit the logout button twice on the frontend to actually destroy the session and log the user out? Is there something timing related that I may be missing here?

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 have NextJS Auth example automatically login after sign-up

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);
}
});

how to 'redirect' with react after login with passport.js?

Newbie react question: I’m using it with passport.js and express. I’m successfully logging in to the application. But I don't know how to do a redirect.
router.post('/login',
passport.authenticate('local'),
function(req, res) {
// If this function gets called, authentication was successful.
res.redirect('/upload'); //<—— what I’d like to do is some kind of redirect here
});
Any help appreciated.
Do that on your callback route.
router.get('/facebook', passport.authenticate('facebook', { scope: ['public_profile', 'email'] }));
router.get('/facebook/callback',
passport.authenticate('facebook', { failureRedirect: "/" }), function (req, res) {
if (req.user || req.session.user)
return res.redirect('/' + req.user._id || req.session.user._id);
return res.redirect('/login');
});
Here, I have used passport.js for Facebook Login. and in /facebook/callback I have redirected to /login and in /login I have a function to check if the session is set.
if (Helper.isLoggedIn(req)) {
res.redirect('/');
return;
}
Definition of isLoggedIn inside Helper.js is
function isLoggedIn(req) {
if (req.session && req.session.user_email && req.session.user_email != null) {
if (!req.user || req.session.user == undefined || req.session.user == null) {
loadUserFrom(req);
}
return true;
}
return false;
}
May be you can consider to do the redirect in react-js after the getting the response from server. Assume you use the redux-thunk.
action.js:
import axios from 'axios';
import { browserHistory } from 'react-router';
export function signinUser({ email, password }) {
return function(dispatch) {
axios.post(`${ROOT_URL}/signin`, { email, password })
.then(response => {
// Successful login, redirect to /upload
browserHistory.push('/upload');
})
.catch(() => {
//handle the error response from server
});
}
}

Resources