with my enterprise we are passing to Keycloak & I'm making test on our existing apps.
Anyway, I am testing actually on a React App which is working to a Symfony API. I created the two clients in Keycloak, with the roles, and users.I adapted the API authenticator for Keycloak, and it's working great.
On front side, I changed the Axios middleware to send the Keycloak's token to the API.
When I'm testing my requests on Insomnia, I get my token and refresh token, and all requests that I'm sending works nicely too ! And on Keycloak's Admin panel the sessions are active as well.
Problem is on client side. I installed keycloak-js and #react-keycloak/web because of the hooks that are usefull for me, to test if user is authenticated, to get roles, to protect pages depending on the role etc...
The thing is when I'm login, I get my tokens, session is active on KC side, but KC-js adapter don't tell user is authenticated, I think because user is not login with keycloak.login which is the Login page of KC. I don't want to log on his page, application already have its own login page. But whatever I'm testing, even I ge my token and the session is well active on KC, the hook keycloak.authenticated is always on false.
Have you any suggests on that ?
index.tsx
import ReactDOM from 'react-dom/client';
import 'bootstrap/dist/css/bootstrap.css'
import './index.css';
import './i18n';
import App from './App';
import i18next from 'i18next';
import { I18nextProvider } from 'react-i18next';
/*------ STORE REDUX PART ------*/
import { Provider, TypedUseSelectorHook } from "react-redux";
import { useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import rootReducer from './Api/Reducers/rootReducer';
import { configureStore, PreloadedState } from "#reduxjs/toolkit"
import { ReactKeycloakProvider } from '#react-keycloak/web';
import keycloak from './components/keycloak';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
export const makeStore = (preloadedState?: PreloadedState<RootState>) => {
return configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware({
immutableCheck: false,
serializableCheck: false
}),
devTools: true,
preloadedState
})
}
const store = makeStore();
const tokenLogger = (tokens: unknown) => {
console.log('onKeycloakTokens', tokens)
}
const keycloakProviderInitConfig = {
onLoad: 'check-sso',
}
root.render(
<ReactKeycloakProvider
authClient={keycloak}
onTokens={tokenLogger}
initOptions={keycloakProviderInitConfig }
>
<Provider store={store}>
<I18nextProvider i18n={i18next}>
<App />
</I18nextProvider>
</Provider>
</ReactKeycloakProvider>
);
export default store;
export type RootState = ReturnType<typeof rootReducer>;
export type AppStore = ReturnType<typeof makeStore>;
export type AppDispatch = typeof store.dispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
export const useAppDispatch = () => useDispatch<AppDispatch>();
intercept.tsx
import axios from "axios";
export const instance = axios.create({
baseURL: process.env.REACT_APP_API_ADDRESS,
headers: {'X-gravitee-api-key': process.env.REACT_APP_API_KEY}
});
const kcInstance = axios.create({
baseURL: process.env.REACT_APP_KEYCLOAK_ADDRESS,
headers: {
'Content-Type' : 'application/x-www-form-urlencoded',
'X-gravitee-api-key': process.env.REACT_APP_API_KEY
}
})
/*----- API Middleware Request -----*/
/*----- Pour tester le SessionID -----*/
kcInstance.interceptors.request.use(
(config) => {
let profile = localStorage.getItem('profile');
let kcStorage = JSON.parse(localStorage.getItem('keycloak'));
if(kcStorage){
let kcToken = kcStorage.access_token;
config.headers['Authorization'] = `Bearer ` + kcToken;
if(profile) {
const userDatas = JSON.parse(profile);
let token = userDatas.sessionId;
let language = localStorage.getItem('language');
let datas = {
sessionId: token,
language: language
}
const bearerInstance = axios.create({
baseURL: process.env.REACT_APP_API_ADDRESS,
headers: {
'X-gravitee-api-key': process.env.REACT_APP_API_KEY,
'Authorization': `Bearer ` + kcToken
}
})
bearerInstance.post('/api/check-session', datas)
.then(res => {
if(res.status === 200) return axios(config);
})
.catch(
(error) => {
if(error.response?.status === 404 && error.response?.data.message.includes("Session")) {
localStorage.clear();
window.location.href= ("/");
}
else if(error.response?.status === 404 && error.response?.data.message.includes("session")) {
localStorage.clear();
window.location.href= ("/");
}
})
}
}
return config;
},
error => {
console.log('error request :' + error)
Promise.reject(error)
});
App.tsx
import { Suspense, useEffect, useState } from 'react';
import SiderLayout from './components/Menu/Layout';
import Assistance from './pages/Assistance';
import ChangePassword from './pages/ChangePassword';
import ForgetPassword from './pages/ForgetPassword';
import Login from './pages/Login';
import RenewIDToken from './pages/RenewIDToken';
import {
BrowserRouter as Router,
Navigate,
Route,
Routes
} from 'react-router-dom'
import ConsultOrder from './pages/ConsultOrder';
import ConsultRepair from './pages/ConsultRepair';
import StockManagement from './pages/StockManagement';
import Consumptions from './pages/Consumptions';
import Contractuals from './pages/Contractuals';
import DetailSerial from './pages/DetailSerial';
import DetailsMortgage from './pages/DetailsMortgage';
import Home from './pages/Home';
import GlobalStyles from './GlobalStyles';
import { useKeycloak } from '#react-keycloak/web';
import PrivateRoute from './Keycloak/PrivateRoute';
import HasAccess from './Keycloak/HasAccess';
function App() {
const { initialized, keycloak } = useKeycloak();
const isLoggedIn = keycloak.authenticated;
if(!initialized) {
return <h3>Chargement... !!!</h3>
}
return (
<Suspense fallback="loading">
<Router>
<GlobalStyles/>
<Routes>
<Route path="/assistance" element={<Assistance/>} />
<Route path="/forget-password" element={<ForgetPassword/>} />
<Route path="/change-password" element={<ChangePassword/>} />
<Route path="/renew-token" element={<RenewIDToken/>} />
{ isLoggedIn ?
<Route path="/" element={<SiderLayout profil={HasAccess(['app-admin'])} />}>
<Route index element={<Home/>}/>
<Route path="/consult-repair" element={<ConsultRepair/>} />
<Route path="/stock-management" element={<StockManagement/>} />
<Route path="/detail-serial/:nno/:sn" element={<DetailSerial/>} />
<Route path="/detail-mortgage/:mortgageNumber" element={<DetailsMortgage/>} />
{ HasAccess(['app-admin']) &&
<>
<PrivateRoute path="/consult-order" component={<ConsultOrder/>} roles={["app-admin"]} />
<Route path="/consumptions-indicators" element={<Consumptions/>} />
<Route path="/contractual-indicators" element={<Contractuals/>} />
</>
}
</Route>
:
<Route path="/" element={<Login/>} /> }
<Route path="*" element={<Navigate to="/" replace/>} />
</Routes>
</Router>
</Suspense>
);
}
export default App;
Related
An unauthenticated user can still see the "/account" page for a few seconds I mean there is still a delay of a few seconds before the unauthenticated user is completely redirected to another page. even though I have used ProtectedRoute, how can I make unauthenticated users unable to see the "/account" page at all without delay? i use react-router-dom 6.6.1 and firebase auth
here is for the app.js
import { ThemeProvider } from '#mui/material/styles';
import React from 'react';
import { BrowserRouter as Router, Route, Routes, Navigate} from 'react-router-dom'
import Home from './components/Home'
import Account from './components/Account';
import Signin from './components/Home/Signin';
import theme from './theme';
import { AuthContextProvider } from './components/Context/AuthContext';
import ProtectedRoute from './components/ProtectedRoute';
function App() {
return (
<ThemeProvider theme={theme}>
<AuthContextProvider>
<Router>
<Routes>
<Route path='/' element={<Home />} />
<Route path='/signin' element={<Signin />} />
<Route path='/account' element={<ProtectedRoute><Account /></ProtectedRoute>} />
</Routes>
</Router>
</AuthContextProvider>
</ThemeProvider>
)
}
export default App
and this is for the ProtectedRoute.js
import React from 'react';
import { Navigate } from 'react-router-dom';
import { UserAuth } from './Context/AuthContext';
const ProtectedRoute = ({ children }) => {
const { user } = UserAuth();
if (!user) {
return <Navigate to='/' />;
}
return children;
};
export default ProtectedRoute;
this if for the UserAuth :
import { createContext, useContext, useEffect, useState } from 'react';
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
onAuthStateChanged,
} from 'firebase/auth';
import { auth } from '../../firebase';
const UserContext = createContext();
export const AuthContextProvider = ({ children }) => {
const [user, setUser] = useState({});
const createUser = (email, password) => {
return createUserWithEmailAndPassword(auth, email, password);
};
const signIn = (email, password) => {
return signInWithEmailAndPassword(auth, email, password)
}
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
setUser(currentUser);
});
return () => {
unsubscribe();
};
}, []);
return (
<UserContext.Provider value={{ createUser, user, signIn }}>
{children}
</UserContext.Provider>
);
};
export const UserAuth = () => {
return useContext(UserContext);
};
I want to redirect unauthenticated users to the main page without delay
it’s probably due to the fact that your guard is validating off of !user however your initial auth state has user as {}. In this case an empty object will validate as truthy.
You could either set the initial state of user as null or you could update th guard validation to check for a value on user like if (!user.id) …
Render Protected and Base routes conditional so unauthenticated user never have access of Protected routes.
Use the following code.
Always make separate file for routing for better code readability and Separation of concerns.
BaseRoutes.js
import React from 'react'
import { Route } from 'react-router-dom'
import Home from './components/Home'
import Signin from './components/Home/Signin';
const BaseRoutes = (
<>
<Route path='/' element={<Home />} />
<Route path='/signin' element={<Signin />} />
</>
)
export default BaseRoutes
ProtectedRoutes.js
import React from 'react';
import { Route } from 'react-router-dom';
import Account from './components/Account';
const ProtectedRoutes = () => {
return (
<>
<Route path='/account' element={<Account />} />
</>
)
};
export default ProtectedRoutes;
Routes.js
import React from 'react'
import { Navigate, Route, Routes, BrowserRouter } from 'react-router-dom'
import { UserAuth } from './Context/AuthContext';
import ProtectedRoutes from './components/ProtectedRoutes';
import BaseRoutes from './components/BaseRoutes';
const Router = () => {
const { user } = UserAuth();
const isUser = Boolean(user)
const redirectUrl = isUser ? '/account' : '/signin'
return (
<BrowserRouter>
<Routes>
{isUser ? <BaseRoutes /> : <ProtectedRoutes />}
<Route
path="*"
element={<Navigate to={redirectUrl} />}
/>
</Routes>
</BrowserRouter>
)
}
export default Router
app.js
import React from 'react';
import { AuthContextProvider } from './components/Context/AuthContext';
import { ThemeProvider } from '#mui/material/styles';
import theme from './theme';
import Router from './routes'
function App() {
return (
<ThemeProvider theme={theme}>
<AuthContextProvider>
<Router />
</AuthContextProvider>
</ThemeProvider>
)
}
export default App
I am using axios interceptors to ask if the access token of a given user is expired and in the case that it is then create a new access token using the refresh token. But there is a problem with an authentication component that I made to check if the user is logged in or not. When the access token is expired and I do a request to generate new tokens I replace the previous Authorization header with the one that just I generated. Now, when I send a post request to check the authentication of the user for some reason that I am too smooth brain to understand the Authorization headers concatenate. For example, it starts off as: authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYyNWIyOTM4OWYxZjZjNGQzYWJjYzljNSIsImVtYWlsIjoiYSIsImlhdCI6MTY1MDU5Mjc5MiwiZXhwIjoxNjUwNTkyNzk3fQ.dSU9B0vz6sUfZ9Ac3e31s78D6aYtWWp5BoxtjlfsaVk, then when I console log it on the backend it becomes: authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYyNWIyOTM4OWYxZjZjNGQzYWJjYzljNSIsImVtYWlsIjoiYSIsImlhdCI6MTY1MDU5Mjc5MiwiZXhwIjoxNjUwNTkyNzk3fQ.dSU9B0vz6sUfZ9Ac3e31s78D6aYtWWp5BoxtjlfsaVk, Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImEiLCJpYXQiOjE2NTA1OTI4MDIsImV4cCI6MTY1MDU5MjgwN30.mww_p7K_lhYLslUBgFUFbGLEr7RQ1e4OiQJvLZFI0_k.
I really do not know what could be causing this. Because if I console log config.headers.authorization is just the new token, but on the api when I console log the header it's concatenated. I have been re-reading the code the whole day to see where I am wrong but I am incapable of knowing what is happening. Any help would be appreciated.
The interceptor=
import Cookies from 'js-cookie';
import { backendURL } from '../config/constants.js'
import axios from 'axios';
import jwt_decode from 'jwt-decode';
const request = axios.create()
request.interceptors.request.use( async (config)=>{
console.log("El interceptor ha sido llamado " + config.url)
let currentDate=new Date();
try {
const encodedToken=Cookies.get("accessToken")
if (!encodedToken || ""){
return config
} else{
const accessToken=jwt_decode(Cookies.get("accessToken"));
if (accessToken.exp *1000 <currentDate.getTime()) {
const axiosRefresh=axios.create()
console.log("Se va a llamar a refresh")
try {
const res = await axiosRefresh.post(backendURL+"UsersDB/refresh",{refreshToken:Cookies.get("refreshToken")})
Cookies.set("accessToken", res.data.accessToken);
Cookies.set("refreshToken", res.data.refreshToken);
console.log(config.headers.authorization)
config.headers.authorization="Bearer "+res.data.accessToken;
console.log(config.headers.authorization)
} catch (error) {
console.log(error)
return Promise.reject(error);
}
}
return config
}
} catch (error) {
console.log(error)
}
});
export default request
The auth component=
import { Component } from "react";
import { BrowserRouter as Outlet , Navigate} from 'react-router-dom';
import Cookies from 'js-cookie';
import { backendURL } from '../config/constants.js'
import request from "../tools/ApiSetup"
class PrivateRoute extends Component {
componentDidMount(){
const header={
"Authorization":"Bearer "+this.state.accessToken
}
const jsonData={}
console.log(header)
request.post(backendURL+"UsersDB/auth",jsonData,{
headers:header
})
.then((response)=>{
// For success, update state like
console.log("Se llamó a auth")
this.setState({ isLoading: false, isLoggedIn: true });
})
.catch((error)=>{
// For fail, update state like
console.log("Se llamó a auth")
this.setState({ isLoading: false, isLoggedIn: false });
});
}
constructor(props) {
super(props);
this.state = {
accessToken:Cookies.get("accessToken") || "",
refreshToken: Cookies.get("refreshToken") || "",
isLoading: true,
isLoggedIn: false
};
}
render() {
return this.state.isLoading ? null :
this.state.isLoggedIn ?
<Outlet /> :
<Navigate to={this.props.redirectTo} />
}
}
export default PrivateRoute;
App.js if you need it for something=
import { BrowserRouter as Router, Route, Routes, Navigate} from 'react-router-dom';
import MyAccount from '../pages/MyAccount/MyAccount';
import Dashboard from '../pages/Dashboard/Dashboard';
import WallPaperWelcome from '../pages/WallpaperWelcome/WallpaperWelcome';
import NewTicket from '../pages/NewTicket/NewTicket';
import MyTickets from '../pages/MyTickets/MyTickets';
import HomeBody from '../components/HomeBody/HomeBody'
import ResetPassword from '../components/ResetPassword/ResetPassword';
import CreateAccount from '../components/CreateAccount/CreateAccount';
import LoginForm from '../components/LoginForm/LoginForm';
import ChangePassword from '../pages/ChangePassword/ChangePassword';
import TicketEditor from '../pages/TicketEditor/TicketEditor';
import './App.css';
import { useEffect, useState } from 'react';
import { createBrowserHistory } from "history";
import PrivateRoute from '../tools/PrivateRoute';
const history = createBrowserHistory();
function App() {
// Hook para lanzar codigo antes que el render
useEffect( () =>{
}, []);
return (
<Router history={history}>
<Routes>
<Route path="/login" exact element={
<>
<div className="App">
<HomeBody />
</div>
</>
}>
<Route index element={<LoginForm/>}/>
<Route path='resetPassword' element={<ResetPassword/>}/>
<Route path='createAccount' element={<CreateAccount/>}/>
</Route>
<Route exact path="/" element={
<PrivateRoute redirectTo="/login"/>
}>
<Route exact path='/' element={<Dashboard/>}>
<Route index element={<WallPaperWelcome/>}/>
<Route path='miCuenta' element={<MyAccount/>}/>
<Route path='misEtiquetas' element={<MyTickets/>}/>
<Route path='nuevoProyecto' element={<NewTicket/>}/>
<Route path='crearEtiqueta' element={<CrearEtiqueta/>}/>
<Route path='cambiarClave' element={<ChangePassword/>}/>
</Route>
</Route>
<Route path='/editarEtiqueta/:id' element={<TicketEditor/>}/>
</Routes>
</Router>
);
}
export default App;
function CrearEtiqueta(){
return(
<h1>Crear Etiqueta</h1>
);
}
Auth backend code=
verifyJwt: function(req,res,next){
console.log(req.headers.authorization)
const authHeader=req.headers.authorization.split(" ")[1];
if (authHeader){
jwt.verify(authHeader,process.env.JWT_SECRET, (err, user)=>{
if (err){
return res.status(403).json("Token is not valid");
}
req.user=user;
next();
})
} else {
res.status(401).json("You are not authenticated!")
}
}
How it is received on the backend=
For the life of me I can't seem to remove the ESlinting warning about my useEffect having a missing dependency fetchProfile(). When I add fetchProfile to the dependency array, I get an endless loop. I would really appreciate any suggestions that could help me stifle this warning. The code is as follows:
import React, { useEffect, useContext, useReducer } from 'react'
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
import './App.css'
import { MuiThemeProvider, createMuiTheme } from '#material-ui/core/styles/'
import UserContext from './contexts/UserContext'
import jwtDecode from 'jwt-decode'
import axios from 'axios'
// utils
import reducer from './utils/reducer'
import themeFile from './utils/theme'
import AuthRoute from './utils/AuthRoute'
import UnAuthRoute from './utils/UnAuthRoute'
// Components
import NavBar from './components/NavBar'
// Pages
import Home from './pages/Home'
import Login from './pages/Login'
import Profile from './pages/Profile'
import SignUp from './pages/SignUp'
import Admin from './pages/Admin'
import Dashboard from './pages/Dashboard'
import Alumni from './pages/Alumni'
// context
import { ProfileContext } from './contexts/ProfileContext'
const theme = createMuiTheme(themeFile)
// axios.defaults.baseURL = `https://us-central1-jobtracker-4f14f.cloudfunctions.net/api`
const App = () => {
const initialState = useContext(UserContext)
const [user, setUser] = useContext(ProfileContext)
const [state, dispatch] = useReducer(reducer, initialState)
const fetchProfile = async token => {
await axios
.get(`/user`, {
headers: {
Authorization: `${token}`
}
})
.then(res => {
setUser(res.data)
})
.catch(err => console.log({ err }))
}
// keeps userContext authorized if signed in
useEffect(
_ => {
const token = localStorage.FBIdToken
if (token && token !== 'Bearer undefined') {
const decodedToken = jwtDecode(token)
if (decodedToken.exp * 1000 < Date.now()) {
localStorage.removeItem('FBIdToken')
dispatch({ type: 'LOGOUT' })
} else {
dispatch({ type: 'LOGIN' })
state.isAuth && fetchProfile(token)
}
} else {
dispatch({ type: 'LOGOUT' })
localStorage.removeItem('FBIdToken')
}
},
[state.isAuth]
)
return (
<MuiThemeProvider theme={theme}>
<UserContext.Provider value={{ state, dispatch }}>
<div className="App">
<Router>
<NavBar isAuth={state.isAuth} />
<div className="container">
<Switch>
<Route exact path="/" component={Home} />
<UnAuthRoute
path="/signup"
component={SignUp}
isAuth={state.isAuth}
/>
<UnAuthRoute
path="/login"
component={Login}
isAuth={state.isAuth}
/>
<AuthRoute
path="/profile"
component={Profile}
isAuth={state.isAuth}
/>
<AuthRoute
path="/dashboard"
component={Dashboard}
isAuth={state.isAuth}
/>
<Route path="/admin" component={Admin} isAuth={state.isAuth} />
<AuthRoute
path="/users/:id"
component={Alumni}
isAuth={state.isAuth}
/>
</Switch>
</div>
</Router>
</div>
</UserContext.Provider>
</MuiThemeProvider>
)
}
export default App
You can do one of two things:
Move fetchProfile out of the component entirely, and use its result instead of having it call setUser directly.
Memoize fetchProfile so you only create a new one when something it depends on changes (which is...never, because fetchProfile only depends on setUser, which is stable). (You'd do this with useMemo or its close cousin useCallback, probably, though in theory useMemo [and thuse useCallback] is for performance enhancement, not "semantic guarantee.")
For me, #1 is your best bet. Outside your component:
const fetchProfile = token => {
return axios
.get(`/user`, {
headers: {
Authorization: `${token}`
}
})
}
then
useEffect(
_ => {
const token = localStorage.FBIdToken
if (token && token !== 'Bearer undefined') {
const decodedToken = jwtDecode(token)
if (decodedToken.exp * 1000 < Date.now()) {
localStorage.removeItem('FBIdToken')
dispatch({ type: 'LOGOUT' })
} else {
dispatch({ type: 'LOGIN' })
if (state.isAuth) { // ***
fetchProfile(token) // ***
.then(res => setUser(res.data)) // ***
.catch(error => console.error(error)) // ***
} // ***
}
} else {
dispatch({ type: 'LOGOUT' })
localStorage.removeItem('FBIdToken')
}
},
[state.isAuth]
)
Since the action is asynchronous, you might want to cancel/disregard it if the component re-renders in the meantime (it depends on your use case).
I've been getting this error for so long and I don't know how to fix this
PrivateRoute Component which is getting an undefined Value from store while using mapstatetoprops
import React from 'react';
import { connect } from 'react-redux';
import { Route, Redirect } from 'react-router-dom';
export const PrivateRoute = ({ isAuthenticated, component: Component, ...rest}) => (
<Route {...rest} component={(props) => (
isAuthenticated ? (
<Component {...props} />
) : (
<Redirect to="/" />
)
)} />
);
const mapStateToProps = (state) => {
return {
isAuthenticated: !!state.auth.uname
};
};
export default connect(mapStateToProps)(PrivateRoute);
AppRouter Component Which is using PrivateRoute for hiding some secret routes from unauthorized access.
import React from 'react';
import { Router, Route, Switch} from 'react-router-dom';
import createHistory from 'history/createBrowserHistory';
import Nav from '../components/navigation/nav';
import Index from '../components/index';
import SignIn from '../components/signin/signin';
import Register from '../components/register/register';
import Home from '../components/home/home';
import { PrivateRoute } from './PrivateRoute';
export const history = createHistory();
const AppRouter = () => {
return(
<Router history={history}>
<div>
<Nav />
<Switch>
<Route exact path='/' component={Index} />
<Route path='/signin' component={SignIn}/>
<Route path='/register' component={Register}/>
<PrivateRoute path='/home' component={Home}/>
</Switch>
</div>
</Router>
);
}
export default AppRouter;
Index.js wrapping store in provider
import React from 'react';
import ReactDOM from 'react-dom';
import { connect } from 'react-redux';
import { Provider } from 'react-redux';
import 'tachyons';
import './index.css';
import App from './App';
import configureStore from './store/configStore';
import * as serviceWorker from './serviceWorker';
const store = configureStore();
const jsx = (
<Provider store={store}>
<App />
</Provider>
)
ReactDOM.render(jsx, document.getElementById('root'));
serviceWorker.unregister();
Auth Reducer
const INIIAL_STATE = {isLoginSuccess: true, uname: '' }
export default (state = INIIAL_STATE, action) => {
switch (action.type) {
case 'LOGIN':
return {
uname: action.uname
};
case 'LOGIN_SUCCESS':
return {
isLoginSuccess: action.isLoginSuccess
};
case 'LOGOUT':
return {};
default:
return state;
}
};
Action Creator
import configureStore from '../store/configStore';
import { history } from '../routers/AppRouter';
const store = configureStore();
export const setLoginSuccess = (isLoginSuccess) => {
return {
type: 'LOGIN_SUCCESS',
isLoginSuccess
};
}
export const login = (uname) => {
return{
type: 'LOGIN',
uname
};
}
export const logout = () => {
return{
type: 'LOGOUT'
};
}
export const startLogin = (username, password) => {
return() => {
fetch('http://localhost:3000/signin' , {
method: 'post',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
username: username,
password: password
})
}).then(response => response.json())
.then(
user => {
if(user) {
store.dispatch(login(user.username));
store.dispatch(setLoginSuccess(true));
if(history.location.pathname === '/signin')
{
history.push('/home');
}
}
})
.catch(err => {console.log(err)});
}
}
I'm currently using redux-persist to persistent data storage in my react-redux application. I have set it up according to the documentation. I have tested it on a separate project with npx create-react-app. The persistent storage was working properly.
When I integrate it in my actual working project, the state saving doesn't work. Can someone help me with this?
Here is my store.js:
import { createStore, applyMiddleware, compose } from 'redux';
import { routerMiddleware } from 'react-router-redux';
import thunk from 'redux-thunk';
import rootReducers from './reducers';
// imports for persistance redux state
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage' // defaults to localStorage for web
const middleware = [thunk, routerMiddleware(history)];
const initialState = {};
//config for redux-persist
const persistConfig = {
key: 'root',
storage,
}
const persistedReducer = persistReducer(persistConfig, rootReducers)
function composeWithApplyMiddlewares() {
if (window.__REDUX_DEVTOOLS_EXTENSION__) {
return compose(applyMiddleware(...middleware), window.__REDUX_DEVTOOLS_EXTENSION__());
}
return compose(applyMiddleware(...middleware));
}
const createMyStore = () =>
createStore(
persistedReducer,
initialState,
composeWithApplyMiddlewares(),
);
let persistor = persistStore(createMyStore());
export default createMyStore();
index.js:
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { syncHistoryWithStore } from 'react-router-redux';
import { Router, Route, browserHistory } from 'react-router';
import RandomID from 'random-id';
import 'normalize.css/normalize.css';
import 'plottable/plottable.css';
import store from './store';
import SignInContainer from './containers/Auth/Signin';
import SignUpContainer from './containers/Auth/Signup';
import ForgotPasswordContainer from './containers/Auth/ForgotPassword';
import ResetPasswordContainer from './containers/Auth/ResetPassword';
import CompleteSignupContainer from './containers/Auth/CompleteSignup';
import UsersListContainer from './containers/Users/List';
import CreateEndpointContainer from './containers/Endpoints/CreateEndpoint';
import TestMessagesContainer from './containers/TestMessages';
import Dashboard from './containers/Dashboard';
import initialize from './actions/initialize';
import Setting from './containers/Setting/Main';
const root = document.getElementById('root');
const history = syncHistoryWithStore(browserHistory, store);
const rand = RandomID(16, 'A0');
localStorage.setItem('authStateKey', rand);
const reduxOauthConfig = {
backend: {
apiUrl: 'http://0.0.0.0:4000', // TODO: Change backend URL here
signOutPath: null, // Add signout path to destroy session token
tokenValidationPath: '/svr/ping',
authProviderPaths: {
facebook: '/svr/auth/facebook',
github: '/svr/auth/github',
gitlab: '/svr/auth/gitlab',
google: '/svr/auth/google',
twitter: '/svr/auth/twitter',
},
},
};
const requireAuth = (nextState, replace) => {
const state = store.getState();
const isSignedIn = state.auth.authUser.isSignedIn;
// const isSignupCompleted = state.auth.authUser.isSignupCompleted;
// Complete signup first before user can do anything
// if (!isSignupCompleted && nextState.location.pathname !== '/complete-signup') {
// replace({
// pathname: '/complete-signup',
// state: { nextPathname: nextState.location.pathname },
// });
// }
if (!isSignedIn) {
replace({
pathname: '/signin',
state: { nextPathname: nextState.location.pathname },
});
}
};
const publicOnly = (nextState, replace) => {
const state = store.getState();
const isSignedIn = state.auth.authUser.isSignedIn;
if (isSignedIn) {
replace({
pathname: '/',
state: { nextPathname: nextState.location.pathname },
});
}
};
store.dispatch(initialize(reduxOauthConfig)).then(
() => {
render(
<Provider store={store}>
<Router history={history}>
<Route exact path="/signup" component={SignUpContainer} onEnter={publicOnly} />
<Route exact path="/signin" component={SignInContainer} onEnter={publicOnly} />
<Route exact path="/forgot-password" component={ForgotPasswordContainer} onEnter={publicOnly} />
<Route exact path="/reset-password" component={ResetPasswordContainer} onEnter={publicOnly} />
<Route exact path="/complete-signup" component={CompleteSignupContainer} onEnter={requireAuth} />
<Route exact path="/users" component={UsersListContainer} onEnter={requireAuth} />
<Route exact path="/chat" component={TestMessagesContainer} onEnter={requireAuth} />
<Route exact path="/add-endpoint" component={CreateEndpointContainer} onEnter={requireAuth} />
<Route exact path="/" component={Dashboard} onEnter={requireAuth} />
<Route exact path="/:userId/setting" component={Setting} onEnter={publicOnly} />
</Router>
</Provider>
, root,
);
},
);
reducers/index.js:
import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux';
import message from './message';
import users from './users';
import endpoints from './endpoints';
import toasts from './toasts';
import auth from './auth';
export default combineReducers({
routing: routerReducer,
users,
endpoints,
ui: toasts,
chat: message,
auth,
});
Thanks for your attention.