I have a login feature using jwt authentication. I currently have the login logout, and profile component handling adding and removing the authentication key to local storage, but I want to consolidate this code into a useToken component.
Login function in Login component:
function logMeIn(event) {
axios({
method: "POST",
url: "/token",
data: {
username: loginForm.username,
password: loginForm.password,
},
})
.then((response) => {
// adds the login token authentication to the local storage
localStorage.setItem("token", response.data.access_token);
})
.catch((error) => {
if (error.response) {
console.log(error.response);
console.log(error.response.status);
console.log(error.response.headers);
}
});
// uses react hook to set the login data
setloginForm({
username: "",
password: "",
});
event.preventDefault();
}
I already have created the useToken component here:
import { useState } from "react";
// retrieves token from local storage on browser
function useToken() {
function getToken() {
const userToken = localStorage.getItem("token");
return userToken && userToken;
}
// token and setToken hook
const [token, setToken] = useState(getToken());
// saves authentication token to browser
function saveToken(userToken) {
localStorage.setItem("token", userToken);
setToken(userToken);
}
// removes token from local storage
// important for logging out!!!
function removetoken() {
localStorage.removeItem("token");
setToken(null);
}
return {
setToken: saveToken,
token,
removetoken,
};
}
export default useToken;
I am having trouble with passing the useToken it as a prop to the Login component. To navigate the app, I am using React router v6 with NavLinks in a navbar component.
What the navbar returns:
return (
<>
<IconContext.Provider value={{ color: "#fff" }}>
<nav className="navbar">
<Link to="/" className="navbar-logo">
<AiOutlineDeploymentUnit className="navbar-icon" />
UNIROUTE
</Link>
<div className="menu-icon" onClick={handleClick}>
{click ? <FaTimes /> : <FaBars />}
</div>
<ul className={click ? "nav-menu active" : "nav-menu"}>
<li className="nav-route">
<NavLink
to="/"
className={({ isActive }) =>
"nav-links" + (isActive ? " activated" : "")
}
>
Get Route
<FaSearchLocation
className="navbar-icon"
onClick={closeMobileMenu}
/>
</NavLink>
</li>
<li className="nav-login">
<NavLink
to="/Login"
className={({ isActive }) =>
"nav-links" + (isActive ? " activated" : "")
}
>
Login
<CgProfile className="navbar-icon" onClick={closeMobileMenu} />
</NavLink>
</li>
<li className="nav-profiel">
<NavLink
to="/Profile"
className={({ isActive }) =>
"nav-links" + (isActive ? " activated" : "")
}
>
Your Profile
<CgProfile className="navbar-icon" onClick={closeMobileMenu} />
</NavLink>
</li>
</ul>
</nav>
</IconContext.Provider>
</>
);
I want to pass useToken as a prop to Login so instead of using:
localStorage.setItem('token', response.data.access_token)
I want it to be :
props.setToken(response.data.access_token);
I tried different ways to pass, useToken through the NavLink, but it always comes up undefined and catches an error. Is there a way to pass the useToken component as a prop via a Navlink?
You havent need to send useToken as a props:
You can import useToken and use from it in all of components .
in your case :
impor useToken for 'path of useToken';
function Login(){
const {setToken} = useToken();
function logMeIn(event) {
axios({
method: "POST",
url: "/token",
data:{
username: loginForm.username,
password: loginForm.password
}
}).then((response) => {
// adds the login token authentication to the local storage
//localStorage.setItem('token', response.data.access_token)
setToken(response.data.access_token);
}).catch((error) => {
if (error.response) {
console.log(error.response)
console.log(error.response.status)
console.log(error.response.headers)
}
})
// uses react hook to set the login data
setloginForm(({
username: "",
password: ""}))
event.preventDefault();
}
return (...)
}
Is that what you meant?
It is better to reconsider using hooks to implement local storage
Related
Hey have someone had this with next-auth, I am trying to make my login authentication with next-auth but it keeps giving me this error and after submit the form it redirects me to /api/auth/error.
my .env has NEXT_PUBLIC_SECRET and NEXTAUTH_URL
Before submit the form the console shows these errors:
My […nextauth] code:
import NextAuth from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import User from '../../utils/user';
import dbConnect from '../../lib/dbConnect';
export default NextAuth({
// Enable JSON Web Tokens since we will not store sessions in our DB
session: {
strategy: 'jwt',
},
// Here we add our login providers - this is where you could add Google or Github SSO as well
providers: [
CredentialsProvider({
type: 'credentials',
// The credentials object is what's used to generate Next Auths default login page - We will not use it however.
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' },
},
// Authorize callback is ran upon calling the signin function
authorize: async (credentials, req) => {
dbConnect();
// Try to find the user and also return the password field
const user = await User.findOne({ email: credentials.email }).select(
'+password'
);
if (!user) {
throw new Error('No user with a matching email was found.');
}
// Use the comparePassword method we defined in our user.js Model file to authenticate
const pwValid = await user.comparePassword(credentials.password);
if (!pwValid) {
throw new Error('Your password is invalid');
}
return user;
},
}),
],
// All of this is just to add user information to be accessible for our app in the token/session
callbacks: {
// We can pass in additional information from the user document MongoDB returns
// This could be avatars, role, display name, etc...
async jwt({ token, user }) {
if (user) {
token.user = {
_id: user._id,
email: user.email,
role: user.role,
};
}
return token;
},
// If we want to access our extra user info from sessions we have to pass it the token here to get them in sync:
session: async ({ session, token }) => {
if (token) {
session.user = token.user;
}
console.log(session);
return session;
},
},
pages: {
// Here you can define your own custom pages for login, recover password, etc.
signIn: '/login', // we are going to use a custom login page (we'll create this in just a second)
},
});
following my Login page code:
import { useState, useRef } from 'react';
import { signIn } from 'next-auth/react';
import { useRouter } from 'next/router';
import { Box, Button, TextField } from '#mui/material';
import { Formik } from 'formik';
import * as yup from 'yup';
const initialValues = {
name: '',
password: '',
};
const userSchema = yup.object().shape({
name: yup.string().required('This is required'),
password: yup.string().required('This is required'),
});
// This goes to our signup API endpoint
async function createUser(email, password) {
const response = await fetch('/api/signup', {
method: 'POST',
body: JSON.stringify({ email, password }),
headers: {
'Content-Type': 'application/json',
},
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.message || 'Something went wrong!');
}
return data;
}
// This gets handled by the [...nextauth] endpoint
function AuthForm() {
const [registered, setRegistered] = useState(false);
const emailInputRef = useRef();
const passwordInputRef = useRef();
// We keep track of whether in a login / or register state
const [isLogin, setIsLogin] = useState(true);
const router = useRouter();
function switchAuthModeHandler() {
setIsLogin((prevState) => !prevState);
}
const submitHandler = async (values, { resetForm }) => {
resetForm();
const enteredEmail = values.name;
const enteredPassword = values.password;
// optional: Add validation here
if (isLogin) {
await signIn('credentials', {
email: enteredEmail,
password: enteredPassword,
redirect: false,
});
} else {
try {
const result = await createUser(enteredEmail, enteredPassword);
setRegistered(true);
} catch (error) {
console.log(error);
}
}
resetForm();
};
return (
<section className="max-w-xl mx-auto my-7">
{!registered ? (
<>
<h1>{isLogin ? 'Login' : 'Sign Up'}</h1>
<Box m="20px">
<Formik
onSubmit={submitHandler}
initialValues={initialValues}
validationSchema={userSchema}
>
{({
values,
errors,
touched,
handleBlur,
handleChange,
handleSubmit,
}) => {
return (
<form onSubmit={handleSubmit}>
<Box
display="grid"
gap="30px"
gridTemplateColumns="repeat(4, minmax(0, 1fr))"
>
<TextField
fullWidth
variant="filled"
type="text"
label="Name"
onBlur={handleBlur}
onChange={handleChange}
value={values.name}
name="name"
error={!!touched.name && !!errors.name}
helperText={touched.firstName && errors.name}
sx={{ gridColumn: 'span 2' }}
/>
<TextField
fullWidth
variant="filled"
type="password"
label="password"
onBlur={handleBlur}
onChange={handleChange}
value={values.password}
name="password"
error={!!touched.password && !!errors.password}
helperText={touched.password && errors.password}
sx={{ gridColumn: 'span 2' }}
/>
</Box>
<Box display="flex" justifyContent="end" mt="20px">
<Button
type="submit"
color="secondary"
variant="contained"
>
{isLogin ? 'Login' : 'Create Account'}
</Button>
<Button
onClick={switchAuthModeHandler}
type="button"
color="secondary"
variant="contained"
>
{isLogin
? 'No Account? Create One'
: 'Already a user? Login'}
</Button>
</Box>
</form>
);
}}
</Formik>
</Box>
</>
) : (
<div className="">
<p>You have successfully registered!</p>
<button
onClick={() => router.reload()}
className="button button-color"
>
Login Now
</button>
</div>
)}
</section>
);
}
export default AuthForm;
I fixed it moving the [...nextauth].js to the /api/auth/ , it was only inside the API folder before
i've been trying since days to redirect my user after login to the home creating a callback function in my App.js and sending it as props to the login class component throught a loginregisterpage class component, but this doesn't work, can someone have a look on it and tell me what i;m missing out?
Thank you my code look like this
App.js
import React from 'react'
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
import { HomePage } from './Pages/HomePage/HomePage'
import { LoginRegisterPage } from './Pages/LoginRegisterPage/LoginRegisterPage'
import 'bootstrap/dist/css/bootstrap.min.css'
export class App extends React.Component {
constructor(props) {
super(props);
this.state = {
authenticated: false,
}
this.handleSuccess = this.handleSuccess.bind(this);
}
handleSuccess = (data) => {
this.props.history.push("/")
}
render() {
return (
<Router>
<Switch>
<Route exact path="/">
<HomePage />
</Route>
<Route exact path="/login-register">
<LoginRegisterPage onLoginSuccess={this.handleSuccess} />
</Switch>
</Router>
)
}
}
LoginRegisterPage class component
class LoginPage extends React.Component {
constructor(props) {
super(props);
this.state = {
username: '',
password: '',
accessToken: '',
authenticated: ''
};
this.handleChangeUsername = this.handleChangeUsername.bind(this);
this.handleChangePassword = this.handleChangePassword.bind(this);
}
handleChangeUsername(event) {
this.setState({
username: event.target.value
})
}
handleChangePassword(event) {
this.setState({
password: event.target.value
})
}
handleClick(event) {
var apiBaseUrl = "https://myapi.com/auth/"
const payload = {
method: "POST",
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
body: JSON.stringify({
'username': this.state.username,
'password': this.state.password
})
};
const { username, password } = this.state;
if (username && password) {
fetch(apiBaseUrl + 'login', payload)
.then((response) => {
if (response.status === 200) {
alert("Logged In! You'll be redirected on Home")
return response.json()
} else {
return alert("wrong pass")
}
}).then((data) => {
this.setState({
accessToken: data.accestToken,
authenticated: data.authenticated
});
localStorage.setItem('accessToken', data.accessToken);
if (data.authenticated === true) {
console.log(this.props)
this.props.onLoginSuccess(data)
}
})
.catch((err) => console.log(err));
} else {
alert("Cannot be Empty")
}
}
render() {
return (
<div>
<div className="form">
<div>
<div className="form-input">
<div >
<div className="userData">
<span>
<img
src={UserIcon}
/>
</span>
<input
autocomplete="off"
type="text"
name="username"
placeholder="Username"
value={this.state.username}
onChange={this.handleChangeUsername}
/>
</div>
<div className="userData">
<span>
<img
src={PasswordIcon}
/>
</span>
<input
autocomplete="off"
type="password"
name="password"
placeholder="Password"
value={this.state.password}
onChange={this.handleChangePassword}
/>
<p style={(this.state.username && this.state.password) ? { display: 'none' } : { display: 'block' }}> Must fill all the form!</p>
</div>
</div>
</div>
</div>
</div>
<div className="form-footer">
<img
src={Btn}
onClick={(event) => this.handleClick(event)}
/>
</div>
</div>
);
}
}
LoginPage class component
class LoginPage extends React.Component {
constructor(props) {
super(props);
this.state = {
username: '',
password: '',
accessToken: '',
authenticated: ''
};
this.handleChangeUsername = this.handleChangeUsername.bind(this);
this.handleChangePassword = this.handleChangePassword.bind(this);
}
handleChangeUsername(event) {
this.setState({
username: event.target.value
})
}
handleChangePassword(event) {
this.setState({
password: event.target.value
})
}
handleClick(event) {
var apiBaseUrl = "https://movies-app-siit.herokuapp.com/auth/"
const payload = {
method: "POST",
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
body: JSON.stringify({
'username': this.state.username,
'password': this.state.password
})
};
const { username, password } = this.state;
if (username && password) {
fetch(apiBaseUrl + 'login', payload)
.then((response) => {
if (response.status === 200) {
alert("Logged In! You'll be redirected on Home")
return response.json()
} else {
return alert("wrong pass")
}
}).then((data) => {
this.setState({
accessToken: data.accestToken,
authenticated: data.authenticated
});
localStorage.setItem('accessToken', data.accessToken);
if (data.authenticated === true) {
console.log(this.props)
this.props.onLoginSuccess(data)
}
})
.catch((err) => console.log(err));
} else {
alert("Cannot be Empty")
}
}
render() {
return (
<div>
<div className="form">
<div>
<div className="form-input">
<div >
<div className="userData">
<span>
<img
src={UserIcon}
/>
</span>
<input
autocomplete="off"
type="text"
name="username"
placeholder="Username"
value={this.state.username}
onChange={this.handleChangeUsername}
/>
</div>
<div className="userData">
<span>
<img
src={PasswordIcon}
/>
</span>
<input
autocomplete="off"
type="password"
name="password"
placeholder="Password"
value={this.state.password}
onChange={this.handleChangePassword}
/>
<p style={(this.state.username && this.state.password) ? { display: 'none' } : { display: 'block' }}> Must fill all the form!</p>
</div>
</div>
</div>
</div>
</div>
<div className="form-footer">
<img
src={Btn}
onClick={(event) => this.handleClick(event)}
/>
</div>
</div>
);
}
}
If you're using React Router you can use the Redirect component:
import { Redirect } from 'react-router-dom';
export default function PrivateRoute () {
if (notLoggedIn()) {
return <Redirect to="/login"/>;
}
// return your component
}
But if you're not inside a render function (i.e. you're in a submit callback) or you want to rewrite browser history, use the useHistory hook (note: hooks work only in function components, not class components)
import { useHistory } from 'react-router-dom';
const history = useHistory();
// After your login action you can redirect with this command:
history.push('/otherRoute');
Issue
App is defined outside the Router component so it has no history prop function to call to do any navigation.
Solution
Have the LoginRegisterPage component navigate upon successful authentication. It will need to access the history object of the nearest Router context. Normally this is achieved by consuming passed route props from the Route component.
You can:
#1
Move LoginRegisterPage to be rendered by the component prop of the Route so it receives the route props and thus the history object as a prop.
<Route exact path="/login-register" component={LoginRegisterPage} />
LoginRegisterPage
class LoginPage extends React.Component {
constructor(props) {
...
}
...
handleClick(event) {
var apiBaseUrl = "https://myapi.com/auth/"
const payload = {...};
const { username, password } = this.state;
const { history } = this.props; // <-- destructure history from props
if (username && password) {
fetch(apiBaseUrl + 'login', payload)
.then((response) => {
...
}).then((data) => {
this.setState({
accessToken: data.accestToken,
authenticated: data.authenticated
});
localStorage.setItem('accessToken', data.accessToken);
if (data.authenticated === true) {
console.log(this.props)
this.props.history.push("/"); // <-- navigate!
}
})
.catch((err) => console.log(err));
} else {
alert("Cannot be Empty")
}
}
render() {
...
}
}
#2
Decorate your LoginRegisterPage with the withRouter Higher Order Component so the route props are injected as props.
import { withRouter } from 'react-router-dom;
...
const LoginPageWithRouter = withRouter(LoginPage);
Note
If you prefer to do a redirect then replace any history.push calls with history.replace. push is a normal navigation and pushes on a new path on the history state whereas replace replaces the current history entry in the stack. After the auth redirect you probably don't want users to back navigate back to your login page/route.
Edit
If you need the handleSuccess callback to manage some auth state in App then I think it best to let App manage the authentication state and the LoginPage to still handle navigation. In this case, go with the second solution above so it receives both the handleSuccess callback and the history object.
if (data.authenticated === true) {
this.props.onLoginSuccess(data); // <-- callback to parent to set state
this.props.history.replace("/"); // <-- imperative navigation
}
Define your handleSucess function in LoginRegisterPage instead of passing it as a prop and this should work.
I'm a bit of a React noob.
I'm trying to create a conditional Redirect, so have written a function (errorCapture) in which one outcome should trigger a Redirect. I'm returning the statement back to a child component (ErrorOrReroute) within the return statement, within my functional component, in the hope that that would cause the page to change. No such luck!
I know the route works under other circumstances.
There is a console.log('redirect'); next to the returned statement in the function, which is displaying as expected, so I know the function is working as intended.
There are no other error messages in the console.
Any help would be greatly appreciated. Thanks!!
(Side question: is it 'ok' to have written a component entirely inside the code for another component? WIll is work? Is it considered dirty coding, if I can be sure it won't need to be used elsewhere? Thanks.)
import PropTypes from 'prop-types';
import AuthContainer from './auth-container';
import { Button, Form, Message, Segment } from 'semantic-ui-react';
import { NavLink, useParams, Redirect } from 'react-router-dom';
import { withFormik } from 'formik';
import { PasswordResetConfirmService } from '../../utils/api/auth/auth.js';
import { mapFormError, NonFieldErrors } from '../../modules/forms';
import { t } from 'ttag';
let uidString = '';
let tokenString = '';
let executed = false;
const PasswordResetConfirmPage = ({ logged, values, handleChange, handleBlur, handleSubmit, isSubmitting, errors }) => {
const { token, uid } = useParams();
uidString = uid;
tokenString = token;
const ErrorOrReroute = () => {
const errorCapture = () => {
if (Object.keys(errors).length === 0) return false;
if (Object.keys(errors).includes('uid') || Object.keys(errors).includes('token')) return true;
};
if (logged && !errorCapture()) {
console.log('redirect');
return <Redirect to={{ pathname: '/' }} />;
} else if (executed && errorCapture()) {
return (
<Message negative>
{'Password update failed :( Please request another password-reset email.'}
</Message>
);
} else return null;
};
return (
<AuthContainer title={t`Reset Password`}>
<Form size="large" onSubmit={handleSubmit}>
<Segment stacked>
<Form.Input fluid icon="lock" iconPosition="left" placeholder={t`New Password`} type="password" name="new_password1" onChange={handleChange} onBlur={handleBlur} value={values.new_password1} error={mapFormError(errors, 'new_password1')} />
<Form.Input fluid icon="lock" iconPosition="left" placeholder={t`Repeat New Password`} type="password" name="new_password2" onChange={handleChange} onBlur={handleBlur} value={values.new_password2} error={mapFormError(errors, 'new_password2')} />
<NonFieldErrors errors={errors} />
<Button primary fluid size="large" type="submit" loading={isSubmitting} disabled={isSubmitting} > {' '} {t`Confirm New Password`}{' '} </Button>
<ErrorOrReroute />
</Segment>
</Form>
<Message> {' '} {t`Know your password?`} <NavLink to="/login">{t`Login`}</NavLink>{' '} </Message>
</AuthContainer>
);
};
const withRegistrationErrors = withFormik({
mapPropsToValues: () => ({ new_password1: '', new_password2: '', uid: '', token: '' }),
handleSubmit: (values, { props, setSubmitting, setErrors }) => {
values.uid = uidString;
values.token = tokenString;
executed = true;
PasswordResetConfirmService(values)
.then(() => {
setSubmitting(false);
props.toggleLoggedState();
})
.catch(errors => {
setErrors(errors);
});
}
});
PasswordResetConfirmPage.propTypes = {
values: PropTypes.object,
handleChange: PropTypes.func,
handleBlur: PropTypes.func,
handleSubmit: PropTypes.func,
isSubmitting: PropTypes.bool,
errors: PropTypes.object,
email: PropTypes.string,
toggleLoggedState: PropTypes.func,
logged: PropTypes.bool
};
export default withRegistrationErrors(PasswordResetConfirmPage);```
I'd recommend using history to change implement the conditional redirect here:
Do so by adding useHistory to your react-router-dom import like this:
import { NavLink, useParams, useHistory } from 'react-router-dom';
// You won't need Redirect anymore
Then under const { token, uid } = useParams(); add this line:
const history = useHistory()
Then finally, replace <Redirect to={{ pathname: '/' }} /> with either:
return history.push('/')
OR
return history.replace('/')
You can read up more about each of those, and decide which one depending on your future use cases, but right now either one of those options will solve your issue here.
Regarding your side question. What you're doing is indeed dirty coding, you should be passing variables via function parameters. Not having those values stored globally in your file like you have done now.
I am using building a react app Here I am unable to figure out how to redirect to /dashboard after successfull login. Any help would be usefull
In this file i am passing my username and password to my redux action this.props.login
signin.js
handleLoginClick = () => {
const { inputUserID, inputPassword } = this.state;
this.login(inputUserID, inputPassword)
};
login = (username, password) => {
this.props.login(username, password);
};
render() {
const {
showError,
open,
inputUserID,
inputPassword,
checkRememberID,
showPhoneNumberDialog,
showVerifyNumberDialog
} = this.state;
return (
<div className="sign-in-dialog-container">
<Dialog
id="dialog-sign-in"
className="dialog"
open={open}
onClose={this.handleClose}
aria-labelledby="form-dialog-title"
style={{
backgroundColor: '#fff'
}}
>
<DialogTitle id="form-dialog-title">Sign In</DialogTitle>
<DialogContent className="dialog-content">
<div className="dialog-content-form">
<div className="form-field">
<div className="content-label">ID</div>
<FormControl fullWidth variant="outlined">
<OutlinedInput
fullWidth
type="text"
placeholder="User Name"
value={inputUserID}
onChange={this.handleTextChange("inputUserID")}
labelWidth={0}
/>
</FormControl>
</div>
<div className="form-field margin-top-16">
<div className="content-label">Password</div>
<FormControl fullWidth variant="outlined">
<OutlinedInput
fullWidth
type="password"
placeholder="**********"
value={inputPassword}
onChange={this.handleTextChange("inputPassword")}
labelWidth={0}
/>
{showError ? (
<FormHelperText className="password-incorrect-text">
Password is incorrect
</FormHelperText>
) : null}
</FormControl>
</div>
<div className="form-field">
<FormControlLabel
control={
<Checkbox
checked={checkRememberID}
onChange={this.handleCheckboxChange("checkRememberID")}
value="checkRememberID"
color="primary"
/>
}
label="Remember ID"
/>
</div>
<div className="form-field">
<Button
className="next-button custom-button-style"
fullWidth
onClick={this.handleLoginClick}
variant="contained"
color="primary"
>
Next
</Button>
</div>
</div>
</DialogContent>
<div className="bottom-row">
<span className="helper-text">
Don't have an account?
<span
onClick={this.handleSignUpClick}
className="strong cursor-pointer"
>
{" "}
Sign Up
</span>
</span>
</div>
</Dialog>
</div>
);
}
}
const mapStateToProps = state => ({
user: state.userReducer,
productPageReducer: state.productPageReducer,
isAuthenticated: state.auth.access_token,
});
const mapDispatchToProps = {
getProductList,
login,
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(SignIn);
authAction.js
export const login = (username, password) => (dispatch) => {
//User Loading:
dispatch({ type: USER_LOADING });
//Make API Call here
var myHeaders = new Headers();
myHeaders.append("Authorization", "Basic dG9wc2VsbGVyOnRvcHNlbGxlcg==");
var formdata = new FormData();
formdata.append("username", username);
formdata.append("password", password);
formdata.append("grant_type", "password");
var requestOptions = {
method: 'POST',
headers: myHeaders,
body: formdata,
redirect: 'follow'
};
fetch("https://api.XXXXXXXXXXXXX.com/oauth/token", requestOptions)
.then(response => response.json())
.then(
res => {
if (res.access_token && res.refresh_token) {
window.localStorage.access_token = res.access_token;
window.localStorage.refresh_token = res.refresh_token;
dispatch({
type: LOGIN_SUCCESS,
user: res
});
} else {
dispatch({
type: LOGIN_FAIL,
message: 'Unauthorised User'
})
}
}
)
.catch(error =>
dispatch({
type: LOGIN_FAIL,
message: error
})
);
}
Here i want to figure out how to redirect to /dashboard after successfull login.I am also using react-router-dom.
You can do this but DO NOT:
CASE LOGIN_SUCCESS:
window.location.href = '/dashboard';
break;
This is just a hack. Redirecting with window.location.href makes your app lose SPA advantages.
Since you're using React-redux, you must hold user login state in reducer. And let App.jsx subscribe login state and if it's false render a Redirect component.
Why your app forgets login state after redirect:
Because you're doing something wrong with window.localStorage:
window.localStorage.whatever_token = 'foobarbaz';
This won't work. localStorage was not meant to be used like this.
Instead, you should:
window.localStorage.setItem('my_token', 'awesome_jwt_token');
Later:
const isLoggedIn = () = > {
const token = window.localStorage.getItem('my_token');
if (token) {
// can ommit this flow
// const isLoggedIn = askServerIfThisTokenIsValid(token);
// if (isLoggedIn) {
// return true;
// } else {
// window.localStorage.removeItem('my_token');
// }
return true; // but not safe - handle 403 error in all api calls
} else {
return false;
}
};
It seems like after successful sign-in attempt, { user } state at your redux store change.
therefore you might consider add this at signin.js
import {Redirect } from 'react-router-dom';
class SignIn extends Component {
...rest class..
render ()
const { user } = this.props;
return(
<>
{ if (user)<Redirect to="/dashboard" />;} // in case { user } is null at initial state, otherewise change the checker...
...rest code...
</>
)
}
working example - https://github.com/zero-to-mastery/visual-music/blob/development/src/pages/Login/Login.js
One way to do this is to use the redux state change to decide which component to render. For instance, you can have a property like 'IsLoggedIn' in the redux state and when its 'true' render the '/dashboard' component. something like:
if(IsLoggedIn) return (<Dashboard />)
else return (<Login />)
My problem is simple, at least it seems. I have state in my redux store that's holding the state of whether the user is logged in or not. That is all working fine, but when the user refreshes the page, for a moment while the authenticated state is async getting it's data, the render runs and state is undefined.
Since, state is undefined, my redirect to /login runs, so the refresh kicks me out of the app and back to login, which then checks to see if I'm already logged in and take me to the homepage.
Any ideas on how to resolve this:
{
!this.props.authenticated && (
<Switch>
<Route path="/login" component={LoginForm} />
<Route path="/register" component={RegisterForm} />
<Route path="" render={props => {
return <Redirect to="/login" />
}}
/>
</Switch>
)
}
So, when this.props.authenticated is false for that short period of time, it hits the login redirect. But, a few ms later, this.props.authenticated is true and since the user is already logged in, is redirected to the home route.
Any ideas?
Ideally you wouldn't render your route straight away but wait until your authentication request has resolved and you have a clear state.
Something like this:
class App extends React.Component {
constructor( props ) {
super( props );
this.state = {
// You could just check if authenticated is null,
// but I think having an extra state makes is more clear
isLoading: true,
authenticated: null,
};
this.checkAuthentication();
}
checkAuthentication() {
// Some async stuff checking against server
// I’ll simulate an async call with this setTimeout
setTimeout(
() => this.setState( {
authenticated: Boolean( Math.round( Math.random() ) ),
isLoading: false,
} ),
1000
);
}
render() {
// Render a loading screen when we don't have a clear state yet
if ( this.state.isLoading ) {
return <div>loading</div>;
}
// Otherwise it is safe to render our routes
return (
<div>
routes,<br />
random authenticated:
<strong>
{ this.state.authenticated.toString() }
</strong>
</div>
);
}
}
ReactDOM.render( (
<App />
), document.querySelector( 'main' ) );
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<main></main>
First of all when user try to login then you receive token in response when user is authenticated. now you have to store token in localStorage using
if(user.token){
localStorage.setItem('user', JSON.stringify(user));
}
it is indicated that when you have token in localstorage you are login otherwise you are logout.
now try to set state to redirect to home page if you want to go home page after login.
this.setState({redirectToReferrer: true});
now return redirect to desire page
if (this.state.redirectToReferrer){
return (<Redirect to={'/home'}/>)
}
login.js
import React from 'react';
import axios from 'axios';
import {Redirect} from 'react-router-dom';
export default class Login extends React.Component{
constructor(props){
super(props);
this.state = {
email : '' ,
password : '',
redirectToReferrer : false
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event){
this.setState({
[event.target.name] : event.target.value
});
}
handleSubmit(event){
event.preventDefault();
const user = {
email : this.state.email,
password : this.state.password
};
if(this.state.email && this.state.password)
{
axios.post(`{Api}/login`,user)
.then((response) =>
{
let userresponse = response;
console.log(userresponse.data);
if(userresponse.token){
sessionStorage.setItem('data',JSON.stringify(userresponse));
this.setState({redirectToReferrer: true});
}
},this)
.catch((error) => alert(error))
}
}
render(){
if (this.state.redirectToReferrer){
return (<Redirect to={'/user'}/>)
}
if (sessionStorage.getItem('data')){
return (<Redirect to={'/user'}/>)
}
return(
<div>
<form ref="formdemo" onSubmit={this.handleSubmit}>
<label>
Username:
<input type="email" name="email" onChange={this.handleChange} placeholder="Enter Your EmailID" required/></label><br/>
<label>
Password :
<input type="password" name="password" onChange={this.handleChange} placeholder="Enter Your Password" required/></label><br/>
<input type="submit"/>
</form>
</div>
)
}
}
Okay, lumio helped get me on the right track with setTimeout, so instead I worked it out with async/await:
class App extends Component {
state = {
error: "",
isLoading: true,
}
async componentDidMount() {
let token = localStorage.getItem('jwtToken');
if (token) {
setAuthToken(token);
await this.props.isAuthenticatedAction(true);
} else {
await this.props.isAuthenticatedAction(false);
}
this.setState({
isLoading: false,
});
}
handleLogout = (evt) => {
evt.preventDefault();
localStorage.removeItem('jwtToken');
window.location.reload();
}
render() {
if (this.state.isLoading) {
return <div></div>;
} else {
// return my regular content
}
You can use react-router-dom for auth work flow.
import React from "react";
import {
BrowserRouter as Router,
Route,
Link,
Redirect,
withRouter
} from "react-router-dom";
////////////////////////////////////////////////////////////
// 1. Click the public page
// 2. Click the protected page
// 3. Log in
// 4. Click the back button, note the URL each time
const AuthExample = () => (
<Router>
<div>
<AuthButton />
<ul>
<li>
<Link to="/public">Public Page</Link>
</li>
<li>
<Link to="/protected">Protected Page</Link>
</li>
</ul>
<Route path="/public" component={Public} />
<Route path="/login" component={Login} />
<PrivateRoute path="/protected" component={Protected} />
</div>
</Router>
);
const fakeAuth = {
isAuthenticated: false,
authenticate(cb) {
this.isAuthenticated = true;
setTimeout(cb, 100); // fake async
},
signout(cb) {
this.isAuthenticated = false;
setTimeout(cb, 100);
}
};
const AuthButton = withRouter(
({ history }) =>
fakeAuth.isAuthenticated ? (
<p>
Welcome!{" "}
<button
onClick={() => {
fakeAuth.signout(() => history.push("/"));
}}
>
Sign out
</button>
</p>
) : (
<p>You are not logged in.</p>
)
);
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props =>
fakeAuth.isAuthenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
const Public = () => <h3>Public</h3>;
const Protected = () => <h3>Protected</h3>;
class Login extends React.Component {
state = {
redirectToReferrer: false
};
login = () => {
fakeAuth.authenticate(() => {
this.setState({ redirectToReferrer: true });
});
};
render() {
const { from } = this.props.location.state || { from: { pathname: "/" } };
const { redirectToReferrer } = this.state;
if (redirectToReferrer) {
return <Redirect to={from} />;
}
return (
<div>
<p>You must log in to view the page at {from.pathname}</p>
<button onClick={this.login}>Log in</button>
</div>
);
}
}
export default AuthExample;
refer link https://reacttraining.com/react-router/web/example/auth-workflow