How to create a protected route after login in reactjs - javascript

I'm quite new in react. I'm trying to create a project where users would have to login to access a page and I want to protect one route that should be accessible only after succesful login. I don't want fancy user management or anything like that so please don't recommend context or redux. I just want my localhost:3000/editor to be accessible only after login and if someone tries to access /editor without login then they are redirected to login page. so something like isAuthenicated: true/false would be fine in my case I believe but I'm not sure how to pass that in my webapp. I would highly if someone can show me how to do that?
I created this but many errors
App.js
import React, {useState} from "react";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";
import "./App.css";
import Login from "./login";
import Dashboard from "./dashboard";
function App() {
const [loggedIN, setLoggedIN]=useState(false);
let privateRoutes = null;
if(loggedIN){
privateRoutes =(
<Route path="/dashboard" component={Dashboard} />
)}
return (
<>
<Router>
<div className="container">
<nav className="navbar navbar-expand-lg navheader">
<div className="collapse navbar-collapse">
<ul className="navbar-nav mr-auto">
<li className="nav-item">
<Link to={"/Login"} className="nav-link">
Login
</Link>
</li>
</ul>
</div>
</nav>
<br />
<Switch>
<Route exact path="/login" component={Login} />
{privateRoutes}
</Switch>
</div>
</Router>
</>);
}
export default App;
login.js
import React, { Component } from "react";
import MuiThemeProvider from "material-ui/styles/MuiThemeProvider";
import AppBar from "material-ui/AppBar";
import RaisedButton from "material-ui/RaisedButton";
import TextField from "material-ui/TextField";
import { Link } from "react-router-dom";
import "./loginForm.css";
class Login extends Component {
constructor(props) {
super(props);
this.state = {
email: "",
password: ""
};
this.onchange = this.onchange.bind(this);
}
onchange(e) {
this.setState({ [e.target.name]: e.target.value });
}
performLogin = async () => {
var body = {
password: this.state.password,
email: this.state.email
};
const options = {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json"
},
body: JSON.stringify(body)
};
const url = "/api/authenticate";
try {
const response = await fetch(url, options);
const text = await response.text();
if (text === "redirect") {
this.props.setState({loggedIN: true})
this.props.history.push(`/dashboard`);
} else {
console.log("login failed");
window.alert("login failed");
}
} catch (error) {
console.error(error);
}
};
render() {
return (
<>
<div className="loginForm">
<MuiThemeProvider>
<TextField
hintText="Enter your Email"
floatingLabelText="Email"
onChange={(event, newValue) => this.setState({ email: newValue })}
/>
<br />
<TextField
type="password"
hintText="Enter your password"
floatingLabelText="password"
onChange={(event, newValue) =>
this.setState({ password: newValue })
}
/>
<br />
<RaisedButton
label="Submit"
primary={true}
style={style}
onClick={event => this.performLogin(event)}
/>
</MuiThemeProvider>
</div>
</>
);
}
}
const style = {
margin: 15
};
export default Login;
editor page
import React, { Component } from "react";
class Dashboard extends Component {
constructor(props) {
super(props);
}
logout=()=>{
this.setState({loggedIN: false)}
}
render() {
return (
<div>hello</div>;
<button onCLick={logout}>Logout</button>
)
}
}
export default Dashboard;
edit: codesandbox

After successfully logged in, maybe you will get token for authorization purpose.
so after successfully logged in you can store the auth token in cookies.
install - npm i universal-cookie --save
login.js
import MuiThemeProvider from "material-ui/styles/MuiThemeProvider";
import AppBar from "material-ui/AppBar";
import RaisedButton from "material-ui/RaisedButton";
import TextField from "material-ui/TextField";
import { Link } from "react-router-dom";
import "./loginForm.css";
import Cookies from 'universal-cookie';
const cookies = new Cookies();
class Login extends Component {
constructor(props) {
super(props);
this.state = {
email: "",
password: ""
};
this.onchange = this.onchange.bind(this);
}
onchange(e) {
this.setState({ [e.target.name]: e.target.value });
}
performLogin = async () => {
var body = {
password: this.state.password,
email: this.state.email
};
const options = {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json"
},
body: JSON.stringify(body)
};
const url = "/api/authenticate";
try {
const response = await fetch(url, options);
const text = await response.text();
// here you will get auth token in response
// set token in cookie like cookie.set('token', response.token);
cookies.set('loggedin', true);
if (text === "redirect") {
this.props.setState({loggedIN: true})
this.props.history.push(`/dashboard`);
} else {
console.log("login failed");
window.alert("login failed");
}
} catch (error) {
console.error(error);
}
};
render() {
return (
<>
<div className="loginForm">
<MuiThemeProvider>
<TextField
hintText="Enter your Email"
floatingLabelText="Email"
onChange={(event, newValue) => this.setState({ email: newValue })}
/>
<br />
<TextField
type="password"
hintText="Enter your password"
floatingLabelText="password"
onChange={(event, newValue) =>
this.setState({ password: newValue })
}
/>
<br />
<RaisedButton
label="Submit"
primary={true}
style={style}
onClick={event => this.performLogin(event)}
/>
</MuiThemeProvider>
</div>
</>
);
}
}
const style = {
margin: 15
};
export default Login;
After that in the dashboard component check for the loggedin boolean in a cookie if it exists then its logged in and authenticated. eg.
dashboard.js
import React, { Component } from "react";
import {Redirect} from 'react-router-dom'
import Cookies from 'universal-cookie';
const cookies = new Cookies();
class Dashboard extends Component {
constructor(props) {
super(props);
}
logout=()=>{
this.setState({loggedIN: false)}
cookies.set('loggedin', false);
}
render() {
// notice here
if (!cookies.get('loggedin')) {
return (<Redirect to={'/login'}/>)
}
return (
<div>hello</div>;
<button onCLick={logout}>Logout</button>
)
}
}
export default Dashboard;

I think you have error here or it is better to change it
let privateRoutes = null;
if(loggedIN){
privateRoutes =(
<Route path="/dashboard" component={Dashboard} />
)}
change it to
let privateRoutes =()=>{
if(loggedIN){
return(
<Route path="/dashboard" component={Dashboard} />
)
}
}
but you don't need this!
you could use render route
<Route path="/home" render={() => {
if(loggedIN){
return(<Dashboard ...props/>)
}else{
return(<Login ...props/>)
}
}} />
note that after login ((loggedIN)) will be true in App.js and it will go to Dashboard automatically
ref:https://reacttraining.com/react-router/web/api/Route/render-func

here you have Big Bugs:
import React, { Component } from "react";
class Dashboard extends Component {
constructor(props) {
super(props);
}
// logout function should not be here it should write in app.js and pass to
// this component with props and just call here if needed
// and you should use this.logout = () ...
logout=()=>{
// and here
this.setState({loggedIN: false**)**}
}
render() {
// here!!! jsx element should wrapped with an element
return (
// <div>
<div>hello</div>;
<button onCLick={logout}>Logout</button>
//</div>
)
}
}
export default Dashboard;

The most efficient way is to split routes and manage redirect with an HOC like [redux-auth-wrapper][2]
App.js
import { createDevTools } from 'redux-devtools'
import LogMonitor from 'redux-devtools-log-monitor'
import DockMonitor from 'redux-devtools-dock-monitor'
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore, combineReducers, applyMiddleware, compose } from 'redux'
import { Provider } from 'react-redux'
import thunkMiddleware from 'redux-thunk'
import * as reducers from './reducers'
import App from './components/App'
const reducer = combineReducers(Object.assign({}, reducers, {}))
const DevTools = createDevTools(
<DockMonitor toggleVisibilityKey="ctrl-h"
changePositionKey="ctrl-q">
<LogMonitor theme="tomorrow" />
</DockMonitor>
)
const enhancer = compose(
// Middleware you want to use in development:
applyMiddleware(thunkMiddleware),
DevTools.instrument()
)
// Note: passing enhancer as the last argument requires redux#>=3.1.0
const store = createStore(reducer, enhancer)
ReactDOM.render(
<Provider store={store}>
<div>
<App />
{/* <DevTools /> */}
</div>
</Provider>,
document.getElementById('mount')
)
auth.js
import locationHelperBuilder from 'redux-auth-wrapper/history4/locationHelper'
import { connectedRouterRedirect } from 'redux-auth-wrapper/history4/redirect'
import connectedAuthWrapper from 'redux-auth-wrapper/connectedAuthWrapper'
import Loading from './components/Loading'
const locationHelper = locationHelperBuilder({})
const userIsAuthenticatedDefaults = {
authenticatedSelector: state => state.user.data !== null,
authenticatingSelector: state => state.user.isLoading,
wrapperDisplayName: 'UserIsAuthenticated'
}
export const userIsAuthenticated = connectedAuthWrapper(userIsAuthenticatedDefaults)
export const userIsAuthenticatedRedir = connectedRouterRedirect({
...userIsAuthenticatedDefaults,
AuthenticatingComponent: Loading,
redirectPath: '/login'
})
export const userIsAdminRedir = connectedRouterRedirect({
redirectPath: '/',
allowRedirectBack: false,
authenticatedSelector: state => state.user.data !== null && state.user.data.isAdmin,
predicate: user => user.isAdmin,
wrapperDisplayName: 'UserIsAdmin'
})
const userIsNotAuthenticatedDefaults = {
// Want to redirect the user when they are done loading and authenticated
authenticatedSelector: state => state.user.data === null && state.user.isLoading === false,
wrapperDisplayName: 'UserIsNotAuthenticated'
}
export const userIsNotAuthenticated = connectedAuthWrapper(userIsNotAuthenticatedDefaults)
export const userIsNotAuthenticatedRedir = connectedRouterRedirect({
...userIsNotAuthenticatedDefaults,
redirectPath: (state, ownProps) => locationHelper.getRedirectQueryParam(ownProps) || '/protected',
allowRedirectBack: false
})
Working example based on React Router v4
What I do
in main routing (usualli App.js):
import { userIsAuthenticated, userIsNotAuthenticated } from './auth';
// ...code
const isNotAuth = userIsNotAuthenticated(Login);
const isAuth = userIsAuthenticated(AuthRoutes);
// ...code
<Switch>
<Route exact path="/login" component={isNotAuth} /> // Notice the *exact* path
<Route path="/" component={isAuth} /> // Notice the *not exact path*
<Route path="" component={NotFoundPage} />
</Switch>
AuthRoutes.js
Those routes will be provided only if the user is authenticated
// ...code
<Switch>
<Route exact path="/homepage" component={HomePage} />
// ...other routes
</Switch>
[2] https://github.com/mjrussell/redux-auth-wrapper

Related

How can I redirect a user to login when a certain link is clicked using JWT

I am trying to make it so that when a user clicks on a certain Link (Upload Component), it will redirect them to to the login component and am not sure how to accomplish this task in React. I was directed to another answered question, but it hasn't helped me as I am still confused on what I need to do with my own set up. I understand I need to make my own protected route (maybe), but I saw others accessing useContext and I do not have any file with context. I am using Version 6 in react dom. I am also using React router and redux in my project so I know I need to access the state somehow, just not sure how to wrap my mind around it so if anyone could help, I would appreciate it. The user is being stored in local storage with JWT authentication.
App.js:
import React, {useState, useEffect, useCallback} from 'react';
import {useDispatch, useSelector} from 'react-redux';
import {BrowserRouter as Router, Routes, Route} from 'react-router-dom'; //Switch was replaced by Routes in react-router-dom v6
import './App.css';
import Navbar from './components/Navbar';
import Footer from './components/Footer';
import Home from './components/pages/Home';
import Uploads from './components/pages/Uploads';
import Account from './components/pages/Account';
import Login from './components/pages/Login';
import LogOut from './components/Logout';
import Register from './components/pages/Register';
import Profile from './components/pages/Profile';
import EditProfile from './components/pages/EditProfile';
import BoardAdmin from './components/pages/BoardAdmin';
import BoardModerator from './components/pages/BoardModerator';
import BoardUser from './components/pages/BoardUser';
import {logout} from './slices/auth';
import EventBus from './common/EventBus';
const App = () => {
const [showModeratorBoard, setShowModeratorBoard] = useState(false);
const [showAdminBoard, setShowAdminBoard] = useState(false);
const {user: currentUser} = useSelector((state) => state.auth);
const dispatch = useDispatch();
useEffect(() => {
if (currentUser) {
setShowModeratorBoard(currentUser.roles.includes('ROLE_MODERATOR'));
setShowAdminBoard(currentUser.roles.includes('ROLE_ADMIN'));
} else {
setShowModeratorBoard(false);
setShowAdminBoard(false);
}
}, [currentUser]);
return (
<>
<Router>
<Navbar />
<Routes>
<Route path='/' element={<Home />} />
<Route path='/create/upload' element={<Uploads />} />
<Route path='/my-account' element={<Account />} />
<Route path='/login' element={<Login />} />
<Route path='/logout' element={<LogOut />} />
<Route path='/register' element={<Register />} />
<Route path='/profile' element={<Profile />} />
<Route path='/profile/edit' element={<EditProfile />} />
<Route path='/user' element={<BoardUser />} />
<Route path='/mod' element={<BoardModerator />} />
<Route path='/admin' element={<BoardAdmin />} />
</Routes>
<Footer />
</Router>
</>
);
};
export default App;
Auth.js:
import {createSlice, createAsyncThunk} from '#reduxjs/toolkit';
import {setMessage} from './messages';
import AuthService from '../services/auth.service';
const user = JSON.parse(localStorage.getItem('user'));
export const register = createAsyncThunk(
'auth/register',
async ({username, email, password}, thunkAPI) => {
try {
const response = await AuthService.register(username, email, password);
thunkAPI.dispatch(setMessage(response.data.message));
return response.data;
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
thunkAPI.dispatch(setMessage(message));
return thunkAPI.rejectWithValue();
}
}
);
export const login = createAsyncThunk(
'auth/login',
async ({username, password}, thunkAPI) => {
try {
const data = await AuthService.login(username, password);
return {user: data};
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
thunkAPI.dispatch(setMessage(message));
return thunkAPI.rejectWithValue();
}
}
);
export const logout = createAsyncThunk('auth/logout', async () => {
await AuthService.logout();
});
const initialState = user
? {isLoggedIn: true, user}
: {isLoggedIn: false, user: null};
const authSlice = createSlice({
name: 'auth',
initialState,
extraReducers: {
[register.fulfilled]: (state, action) => {
state.isLoggedIn = false;
},
[register.rejected]: (state, action) => {
state.isLoggedIn = false;
},
[login.fulfilled]: (state, action) => {
state.isLoggedIn = true;
state.user = action.payload.user;
},
[login.rejected]: (state, action) => {
state.isLoggedIn = false;
state.user = null;
},
[logout.fulfilled]: (state, action) => {
state.isLoggedIn = false;
state.user = null;
},
},
});
const {reducer} = authSlice;
export default reducer;
It looks like adding the if statement in my upload file is making it so that a logged in user can access the h1 element that is in the file, but not an unauthenticated user. So its working to an extent, just not redirecting back to login.
Upload.jsx:
import React from 'react';
import {Link} from 'react-router-dom';
import {useSelector} from 'react-redux';
function Uploads() {
const {user: currentUser} = useSelector((state) => state.auth);
if (!currentUser) {
return <Link to='/login' />;
}
return (
<>
<h1 className='page'>UPLOADS</h1>
</>
);
}
export default Uploads;
Rendering the Link doesn't imperatively navigate, use the Navigation component.
Example:
import React, { useEffect } from 'react';
import { Navigate } from 'react-router-dom';
import { useSelector } from 'react-redux';
function Uploads() {
const { user: currentUser } = useSelector((state) => state.auth);
if (!currentUser) {
return <Navigate to='/login' replace />;
}
return (
<>
<h1 className='page'>UPLOADS</h1>
</>
);
}
export default Uploads;
Typically you would protect the route instead. If you wanted to do that:
import { Navigate, Outlet } from 'react-router-dom';
import { useSelector } from 'react-redux';
const PrivateRoutes = () => {
const { user: currentUser } = useSelector((state) => state.auth);
return currentUser
? <Outlet />
: <Navigate to="/login" replace />;
}
...
<Routes>
<Route path='/' element={<Home />} />
<Route element={<PrivateRoutes />}>
<Route path='/create/upload' element={<Uploads />} />
... any other protected routes ...
</Route>
... other unprotected routes ...
</Routes>

Warning: Can't perform a React state update on an unmounted component when login with auth0

I am following a tutorial from https://auth0.com/blog/role-based-access-control-rbac-and-react-apps/ and it seems that author doesn't support it anymore.
The idea is simple: Once a user presses Login button (on Header.js) he is redirected to auth0 page. There he enters his data and is redirected back to localhost:3000/callback route. This is when handleAuthentication is triggered.
Unfortunately, I am facing an issue when setting the state when the setSession function is used.
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
in Auth (created by App)
in App
Here are the components:
App.js
import React from 'react'
import { Switch, Route, BrowserRouter as Router } from 'react-router-dom'
import Auth from './Auth';
import CallbackPage from "../pages/callback";
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<Auth>
<div style={{width: '1280px', margin: '0 auto'}}>
<Router>
<Switch>
<Route exact path='/' component={HomePage} />
<Route path='/callback' component={CallbackPage} />
</Switch>
</Router>
</div>
</Auth>
)
}
}
export default App;
Auth.js
import React, {Component} from 'react';
import auth0 from 'auth0-js';
import {AUTH_CONFIG} from '../auth0-variables';
import {AuthProvider} from '../authContext';
const auth = new auth0.WebAuth({
domain: AUTH_CONFIG.domain,
clientID: AUTH_CONFIG.clientId,
redirectUri: AUTH_CONFIG.callbackUrl,
audience: `https://${AUTH_CONFIG.domain}/userinfo`,
responseType: 'token id_token'
});
class Auth extends Component {
state = {
authenticated: false,
}
initiateLogin = () => {
auth.authorize();
};
handleAuthentication = () => {
auth.parseHash((error, authResult) => {
if (error) {
console.log(error);
console.log(`Error ${error.error} occured`);
return
}
this.setSession(authResult);
})
};
setSession(authResult) {
this.setState({
// This does not update the state!!
authenticated: true,
});
};
render() {
const authProviderValue = {
...this.state,
initiateLogin: this.initiateLogin,
handleAuthentication: this.handleAuthentication,
};
return (
<AuthProvider value={authProviderValue}>
{this.props.children}
</AuthProvider>
)
}
};
export default Auth;
Header.js (Can component can be found at https://github.com/itaditya/react-rbac-auth0-article-code/blob/master/src/components/Can.js)
import React, { useEffect, useReducer } from 'react';
import {
BrowserRouter as Router,
Link }
from 'react-router-dom';
import Login from '../Login';
import Logout from '../Logout';
import Can from '../Can';
import { AuthConsumer } from '../../authContext';
const Header = (props) => {
return (
<AuthConsumer>
{({ user }) => (
<Can role={user.role} perform='home-page:seeLogin'
yes={() => (
<Login />
)}
no={() => (
<Logout />
)}
/>
</AuthConsumer>
)
}
export default Header
And pages:
homePage:
import React from 'react';
const HomePage = () => {
return (
<div>
<Header />
</div>
)
};
export default HomePage;
Callback page
import React from 'react';
import { Redirect } from 'react-router-dom';
import { AuthConsumer } from '../authContext';
const Callback = props => (
<AuthConsumer>
{({ handleAuthentication }) => {
if (/access_token|id_token|error/.test(props.location.hash)) {
handleAuthentication();
}
return <Redirect to='/' />;
}}
</AuthConsumer>
);
export default Callback;
Any help would be appreciated.

On successful login, this.props.history.push does not take to new page as ProtectedRoute is not successful

After I receive a 200 status code, I try to go to the dashboard page. However, it does not work as intended. I am using React Context and ProtectedRoute. When I get the successful response, I call the login() function and it makes isAuthenticated as true but still does not take me to the dashboard Page.
PS: I checked and saw that in my protected route, it goes to the second part and gets redirected to "/" and hence I am on the same page. Do not know why that is happening
Signin
import { AuthContext } from './AuthContext'
async handleSubmit(event) {
//I send the request before this
const status = await res.status
const json = await res.json();
if(status === 200) {
const {login} = this.context;
login();
this.props.history.push('/dashBoard')
console.log('done')
} else {
console.log('not done')
}
} catch (error) {
console.log('Error', error)
}
}
static contextType = AuthContext;
render() {
return (
<Container className="formlogin">
<div>
<Form className = "signinform backg-sheet1" onSubmit={this.handleSubmit}>
<div>
<h3 style = {{color: 'white', textAlign: 'center'}}> SIGN IN </h3>
<Form.Group controlId = "formBasicUsername">
<Form.Label style = {{color: 'white'}}>Username</Form.Label>
<Form.Control required name = "username" type = "text" placeholder = "Enter username" onChange = {this.handleChange}/>
</Form.Group>
<Form.Group controlId = "formBasicPassword">
<Form.Label style = {{color: 'white'}}>Password</Form.Label>
<Form.Control required name = "password" type = "password" placeholder = "Enter password" onChange = {this.handleChange}/>
</Form.Group>
<div style={{textAlign: 'center'}}>
<Button variant="warning" type="submit">
Submit
</Button>
</div>
</div>
</Form>
</div>
</Container>
);
}
}
export default withRouter(SignIn);
App.js
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Router>
<AuthProvider>
<NavigationBar />
<Switch>
<Route exact path = '/'
component = {LandingPage}
/>
<Route exact path = '/register'
component = {Register}
/>
<ProtectedRoute exact path = '/dashBoard'
component = {dashBoard}
/>
</Switch>
</AuthProvider>
</Router>
</div>
);
}
}
export default App;
AuthProvider
import React, { Component} from 'react';
export const AuthContext = React.createContext();
class AuthProvider extends Component {
state = {
isAuthenticated: false
}
constructor() {
super()
this.login = this.login.bind(this)
this.logout = this.logout.bind(this)
}
login() {
setTimeout(() => this.setState({ isAuthenticated: true }), 1000)
}
logout() {
this.setState({ isAuthenticated: false })
}
render() {
console.log(this.state.isAuthenticated)
return(
<AuthContext.Provider value={{isAuthenticated: this.state.isAuthenticated, login: this.login, logout: this.logout}}>
{this.props.children}
</AuthContext.Provider>
)
}
}
const AuthConsumer = AuthContext.Consumer
export { AuthProvider, AuthConsumer }
ProtectedRoute
import React from 'react'
import { Route, Redirect } from 'react-router-dom'
import { AuthConsumer } from './AuthContext'
const ProtectedRoute = ({component: Component, ...rest}) => (
<AuthConsumer>
{({ isAuthenticated }) => (
<Route
render={(props) =>
isAuthenticated?<Component {...props} /> : <Redirect to="/" />
}
{...rest}
/>
)}
</AuthConsumer>
)
export default ProtectedRoute
LandingPage
import React from 'react';
import SignIn from './SignIn'
import NavigationBar from './NavigationBar'
import {Row} from 'react-bootstrap';
import './Styles.css'
class LandingPage extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<div className = "backg">
<div className = "backg-sheet">
<SignIn />
</div>
</div>
</div>
)
}
}
export default LandingPage
Make few corrections and you will successfully get navigated to dashboard page.
Your handleSubmit is a normal function and you are losing the context of this. So make it an arrow function
Use event.preventDefault else browser will reload and you end up back to / page.
I have placed your code in the sandbox and made it work.
Code snippet
handleSubmit = async event => {
try {
event.preventDefault();
//I send the request before this
// console.log("this.context", this.context); //<---- prints successfully
const status = 200; //await res.status;
// const json = await res.json();
if (status === 200) {
const { login } = this.context;
await login();
console.log("this.context", this.context);
this.props.history.push("/dashBoard");
console.log("done");
} else {
console.log("not done");
}
} catch (error) {
console.log("Error", error);
}
};
You need to implement withRouter component in order for you have access with this.props.hostory... variable just like this below.
import { withRouter } from 'react-router';
export default withRouter(Login);

Sign out and always pop up asking for account google firebase

I'm working with React and firebase, I'm capable of log in into the app with Google, and sign out as well, but if I press login again instead of asking me for an account, logged me with my previous user.
What I want is: when I sign out and want to log me again, the popup ask me for an account.
I've try almost all solution from internet, the most I tried is:
firebase.auth().signOut(), firebase.getInstance.auth().signOut(), and when I want to log again, the app log me right away, I want to ask for an account, I cant login without any other account.
import React from 'react';
import './App.scss';
import { BrowserRouter as Router, Switch, Route, Redirect } from 'react-router-dom';
import Navbar from './components/Navigation/'
import Home from './components/Home'
import MiCuenta from './components/MiCuenta'
/* import ListUsers from './components/ListUsers' */
import MyAccount from './components/MyAccount'
import SignIn from './components/SignIn'
import withFirebaseAuth from 'react-with-firebase-auth'
import * as firebase from 'firebase';
import 'firebase/auth';
import firebaseConfig from './components/FirebaseConfig/';
const firebaseApp = firebase.initializeApp(firebaseConfig);
const firebaseAppAuth = firebaseApp.auth(firebaseApp);
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
user: {
username: '',
edad: Number
},
}
this.logout = () => firebase.auth().signOut()
.then(function() {
})
.catch(function(error) {
});
this.loadData = this.loadData.bind(this);
}
loadData(){
if(this.props.user){
console.log(this.props.user.uid)
let userRef = firebase.database().ref(`users`)
let currentUser = userRef.child(this.props.user.uid);
currentUser.on('value', data => {
if (data.exists()) {
this.setState({ user: data.val() })
console.log(this.state.user)
}
else {
firebase.database().ref('users/' + this.props.user.uid).set({
username: this.props.user.displayName,
nombre: 'string',
edad: 0,
hobbies: ['futbol', 'tenis']
});
}
})
} else{
console.log('not user')
}
}
componentDidUpdate(prevProps) {
if (this.props.user !== prevProps.user) {
this.loadData()
}
}
render() {
const {
user,
signOut ,
signInWithGoogle,
} = this.props;
return (
<div className="App">
{
user
?
<div>
<p>Hello, {this.state.user.username}</p>
<p>{this.state.user.edad}</p>
</div>
: <p>Please sign in. </p>
}
{
user
?
<React.Fragment>
<Router>
<div>
<button onClick={this.loadData}>loadData</button>
<Navbar signOut={this.logout} />
<Switch>
<Route path="/login" component={SignIn} />
<Route path="/Home" component={Home} />
<Route path="/MiCuenta" component={MiCuenta} />
<Route path="/MyAccount" component={() => <MyAccount userObject={this.state.user} />} />
{/* <Route exact path={"/ListUsers"} component={() => <ListUsers uid={user.uid} displayName={user.displayName} /> } /> */}
</Switch>
</div>
</Router>
</React.Fragment>
:
<div>
<button onClick={signInWithGoogle}>Sign in with Google</button>
</div>
}
</div>
);
}
}
const providers = {
googleProvider: new firebase.auth.GoogleAuthProvider(),
};
// high order component
export default withFirebaseAuth({
providers,
firebaseAppAuth,
})(App);
setCustomParameters works for me
my version: "firebase": "^9.6.1",
function googleSignIn() {
const provider = new GoogleAuthProvider();
// select account
provider.setCustomParameters({
prompt: "select_account",
});
return signInWithPopup(auth, provider);
}

React refresh component on login

I have 2 components, NavBar which contains a login modal and the 'body' of page.
I want to detect when a user logs in and re-render the page based on that. How do I update the login prop in the second component when I log in using the modal of the first one?
A simplified version of the code to keep it short:
// NavBar.js
export default class NavBar extends Component {
constructor(props) {
super(props)
this.initialState = {
username: "",
password: "",
loginModal: false
}
this.handleLogin = this.handleLogin.bind(this)
}
handleLogin(e) {
e.preventDefault()
loginAPI.then(result)
}
render() {
return( <nav> nav bar with links and login button </nav>)
}
// Some random page
export default class Checkout extends Component {
constructor(props) {
super(props);
this.state = {
order_type: 'none',
loggedIn: false
}
this.Auth = new AuthService()
}
componentDidMount() {
if (this.Auth.loggedIn()) {
const { username, email } = this.Auth.getProfile()
this.setState({ loggedIn: true, email: email })
}
try {
const { order_type } = this.props.location.state[0]
if (order_type) {
this.setState({ order_type: order_type })
}
} catch (error) {
console.log('No package selected')
}
}
componentDidUpdate(prevProps, prevState) {
console.log("this.props, prevState)
if (this.props.loggedIn !== prevProps.loggedIn) {
console.log('foo bar')
}
}
render() {
return (
<section id='checkout'>
User is {this.state.loggedIn ? 'Looged in' : 'logged out'}
</section>
)
}
}
// App.js
function App() {
return (
<div>
<NavBar />
<Routes /> // This contains routes.js
<Footer />
</div>
);
}
// routes.js
const Routes = () => (
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/register" component={Register} />
<Route exact path="/registersuccess" component={RegisterSuccess} />
<Route exact path="/faq" component={FAQ} />
<Route exact path="/checkout" component={Checkout} />
<Route exact path="/contact" component={Contact} />
{/* <PrivateRoute exact path="/dashboard" component={Dashboard} /> */}
<Route path="/(notfound|[\s\S]*)/" component={NotFound} />
</Switch>
)
I would recommend using the react context API to store information about the logged in user.
See: https://reactjs.org/docs/context.html
Example
auth-context.js
import React from 'react'
const AuthContext = React.createContext(null);
export default AuthContext
index.js
import React, { useState } from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import AuthContext from './auth-context.js'
const AppWrapper = () => {
const [loggedIn, setLoggedIn] = useState(false)
return (
<AuthContext.Provider value={{ loggedIn, setLoggedIn }}>
<App />
</AuthContext.Provider>
)
}
ReactDOM.render(
<AppWrapper/>,
document.querySelector('#app')
)
Then inside any component you can import the AuthContext and use the Consumer component to check if the user is logged in order set the logged in state.
NavBar.js
import React from 'react'
import AuthContext from './auth-context.js'
const NavBar = () => (
<AuthContext.Consumer>
{({ loggedIn, setLoggedIn }) => (
<>
<h1>{loggedIn ? 'Welcome' : 'Log in'}</h1>
{!loggedIn && (
<button onClick={() => setLoggedIn(true)}>Login</button>
)}
</>
)}
</AuthContext.Consumer>
)
export default NavBar
HOC version
with-auth-props.js
import React from 'react'
import AuthContext from './auth-context'
const withAuthProps = (Component) => {
return (props) => (
<AuthContext.Consumer>
{({ loggedIn, setLoggedIn }) => (
<Component
loggedIn={loggedIn}
setLoggedIn={setLoggedIn}
{...props}
/>
)}
</AuthContext.Consumer>
)
}
export default withAuthProps
TestComponent.js
import React from 'react'
import withAuthProps from './with-auth-props'
const TestComponent = ({ loggedIn, setLoggedIn }) => (
<div>
<h1>{loggedIn ? 'Welcome' : 'Log in'}</h1>
{!loggedIn && (
<button onClick={() => setLoggedIn(true)}>Login</button>
)}
</div>
)
export default withAuthProps(TestComponent)
Alternatively if you have redux setup with react-redux then it will use the context API behind the scenes. So you can use the connect HOC to wrap map the logged in state to any component props.

Categories

Resources