keycloak-js client in PWA not using cached token from storage - keycloak

I would like to authenticate my react app - PWA based on create-react-app with keycloak server, and once user is authenticated then i would like them to be able to browser side offline with valid token that i have cached in localStorage but even when passed tokens to keycloak init, i can see user redirected to keycloak auth server.
library version:-
"keycloak-js": "^12.0.2",
"#react-keycloak/web": "^3.4.0",
App.tsx
class App extends React.Component {
tokens: any;
constructor(props: any) {
super(props);
this.tokens = JSON.parse(localStorage.getItem(kcTokens) || '{}');
}
onTokens = (tokens: Pick<AuthClient, "idToken" | "refreshToken" | "token">) => {
localStorage.setItem(kcTokens, JSON.stringify(tokens));
}
onEvent = (event: AuthClientEvent, error?: AuthClientError | undefined) => {
console.log('onKeycloakEvent', event, error);
}
render() {
return (
<ReactKeycloakProvider
authClient={keycloak}
initOptions={{
onLoad: 'check-sso',
...this.tokens
}}
LoadingComponent={loadingComponent}
onEvent={this.onEvent}
onTokens={this.onTokens}
>
<AppRouter />
</ReactKeycloakProvider>
)
}
}
AppRouter.tsx
const AppRouter = () => {
return (
<Router history={history}>
<Switch>
<PrivateRoute path="/" exact component={Home}></PrivateRoute>
</Switch>
</Router>
)
}
PrivateRoute.tsx
const PrivateRoute : React.FC<PrivateRouteProps> = ({ component: Component, ...rest }) => {
const { keycloak } = useKeycloak();
React.useEffect(() => {
if (!keycloak?.authenticated) {
keycloak.login();
}
}, [keycloak]);
return (
<Route
{...rest}
render={props => (
keycloak?.authenticated && <Component {...props} />
)}
/>
);
}

Related

GatsbyJs and Material-UI css is lost on page refresh

I have a gatsby site which runs fine on develop mode but whenever the site is built and deployed and the page is refreshed the site renders erratically.
I suspect the header component which has a window.innerwidth query is the cause.
Here is my code:
export default function Header(props) {
const [state, setState] = useState({ mobileView: true })
const { mobileView } = state
const classes = useStyles()
useEffect(() => {
const setResponsiveness = () => {
return window.innerWidth < 900
? setState((prevState) => ({ ...prevState, mobileView: true }))
: setState((prevState) => ({ ...prevState, mobileView: false }));
};
setResponsiveness();
window.addEventListener("resize", () => setResponsiveness());
return () => {
window.removeEventListener("resize", () => setResponsiveness());
}
}, []);
const MobileMenu = () => {...some code}
const DesktopHeader = () => {...some code}
}
return (
<header>
<AppBar className={classes.root} elevation={0} position='static'>
{mobileView ? <MobileMenu /> : <DesktopHeader />}
</AppBar>
</header>
)
}
```
The link to the deployed site can be found here: [Working Site][1]
[1]: https://brit-tr.com

Storing session in Browser for express.js (MERN Stack)

I've been trying to store a user's email in the session for my express.js file. But everytime I try something, and call another function, the session remains undefined. I wanted to store it in the browser only without storing each session in the database. Been working on this for weeks now, and I can't seem to find the answer.
server.js file:
import express from 'express';
import cookieParser from 'cookie-parser';
import session from 'express-session';
const app = express();
app.use(cookieParser());
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true,
cookie: {
maxAge : 1000 * 60 * 60 * 3, // if 1 day * 24 but since *3 its for 3 hours only
},
}))
app.post('/user/login', (req, res) => {
const loginUser = req.body;
const email = loginUser['email'];
const password = loginUser['password'];
if (!email || !password){
res.status(400).json({success: false, error: "Please provide email and password"});
}
try {
Users.findOne({ email: email }, (err, user) => {
if (password == user['password']){
req.session.user(user['email']);
res.status(200).send(user_email);
} else {
res.status(400).json({success: false, error: "incorrect password"});
}
});
} catch {
}
})
Calling the Login file from the frontend (react js):
import React, { useState } from 'react';
import { Grid, TextField, Button } from '#mui/material';
import "./SignUpLogin.css";
import axios from '../../axios';
import useForm from './useForm';
import { Form } from './useForm';
const initialValues = {
email: '',
password: ''
}
function Login({ modalFunc }) {
const LoginUser = e => {
console.log("INSIDE LOGIN USER");
modalFunc();
e.preventDefault();
axios.post('/user/login', values, {withCredentials: true})
.then(response => {
console.log("in login user");
console.log(response.data);
})
}
const {
values,
setValues,
handleInputChange
} = useForm(initialValues);
return (
<div className="Login">
<Form>
<Grid item>
<TextField
required
variant="outlined"
label="Email"
name="email"
color="secondary"
fullWidth
value={ values.email }
onChange={ handleInputChange }
/>
</Grid>
<Grid item>
<TextField
required
variant="outlined"
label="Password"
name="password"
type="password"
color="secondary"
fullWidth
value={ values.password }
onChange={ handleInputChange }
/>
</Grid>
<Grid item>
<Button
variant="contained"
fullWidth
onClick = { LoginUser }>
Login
</Button>
</Grid>
</Form>
</div>
)
}
export default Login
But when I call server.js again in another get function, session is undefined.
app.get('/user/loggedIn', (req, res) => {
console.log(req.session.user);
user_email = req.session.user;
if (req.session.user) {
Users.findOne({ email: user_email }, (err, user) => {
// console.log("in logged in, in server!!!");
// console.log(user);
res.status(200).send(user);
})
} else {
console.log("no session");
res.status(400);
}
})
Calling app.get('/user/loggedIn') in react.js file:
function Header() {
const [modalOpen, setModalOpen] = useState(false);
const changeModal = () => {
setModalOpen(!modalOpen)
}
const [user, setUser] = useState(null);
useEffect(() => {
// axios.get('/user/loggedIn', {}, {withCredentials: true})
axios.get('/user/loggedIn', {}, {withCredentials: true})
.then(response => {
// console.log("RESPONSE FROM LOGGED IN");
// console.log(response.data);
setUser(response.data);
})
})
server.js
app.use(
cors({
origin: "http://localhost:3000",
credentials: true,
})
);
withCredentials should be defined this way
axios.defaults.withCredentials = true;

nextjs errors with form handling

I'm learning react and nextjs, the page works perfectly without the integration of a UI library, since i installed react suite my page with the login form doesn't work anymore, it returns me an error: TypeError: Cannot read property 'value 'of undefined
My code is:
import React, { useState } from "react"
import { setCookie, parseCookies } from "nookies"
import { useRouter } from "next/router"
import { Form, FormGroup, FormControl, ControlLabel, HelpBlock, Button, Input } from 'rsuite';
const Login = () => {
// set initial const
const router = useRouter()
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
// handle event submit
const handleSubmit = async (e) => {
e.preventDefault();
// fetch user
const response = await fetch( process.env.urlHeadless + '/api/cockpit/authUser', {
method: "post",
headers: {
"Content-Type": "application/json",
'Cockpit-Token': process.env.loginKey,
},
body: JSON.stringify({
user: username,
password: password
})
})
// if user exists
if (response.ok) {
const loggedUser = await response.json()
// set cookie with api_key
setCookie("", "tokenId", loggedUser._id, {
maxAge: 30 * 24 * 60 * 60,
path: "/"
})
// redirect to dashboard
return (router.push("/dashboard"))
} else if (response.status === 412) {
alert('compila il form')
} else if (response.status === 401) {
alert('credenziali di accesso errate')
}
}
return (
<>
<Form>
<FormGroup>
<ControlLabel>Username</ControlLabel>
<FormControl type="text" value={username} onChange={(e) => setUsername(e.target.value)} />
<HelpBlock>Required</HelpBlock>
</FormGroup>
<FormGroup>
<ControlLabel>Password</ControlLabel>
<FormControl type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
</FormGroup>
<FormGroup>
<Button appearance="primary" onClick={handleSubmit}>Submit</Button>
</FormGroup>
</Form>
</>
)
}
// async function
export async function getServerSideProps(context) {
// get cookie value
const cookies = parseCookies(context).token;
// if cookie has value
if (cookies) {
return {
// redirect to dashboard
redirect: {
destination: '/dashboard',
permanent: false,
},
}
}
return { props: {} };
}
export default Login;
I also tried using useRef instead of useState, but it didn't work ... where am I wrong? thank you in advance
Looks like rsuite components might have a different onChange implementation, as described in their docs
onChange (formValue:Object, event:Object) => void Callback fired when data changing
If you want the event, try changing the onChange callback in your code to take the second parameter:
<FormControl
...
onChange={(value, e) => setUsername(e.target.value)}
/>

IonRouter or react-router-dom Not Passing props.match via PrivateRoute

I am trying to access url params from inside a component in a project using IonReactRouter. For some reason the match param in props is just not present. All other props are present however.
I am using a PrivateRoute using what I believe to be the standard react-router-dom PrivateRoute implementation.
PrivateRoute:
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { connect } from "react-redux";
// A wrapper for <Route> that redirects to the login
// screen if you're not yet authenticated.
const PrivateRoute = ({ Component, ...rest }) => {
const { user } = rest;
return (
<Route
{...rest}
render={(props) => {
if (user !== null) {
// return React.cloneElement(children, { ...rest });
return <Component {...props} />
}
return (
<Redirect
to={{
pathname: "/",
state: { from: props.location },
}}
/>
);
}}
/>
);
};
const mapStateToProps = (state) => {
return {
user: state.auth.user,
};
};
export default connect(mapStateToProps)(PrivateRoute);
App.js routes:
return (
<IonApp className="app">
<IonReactRouter>
<Switch>
<Route
exact
path="/"
render={(props) => {
return appLoading == true && user == null ? (
<Loader />
) : user !== null ? (
<Dashboard {...props} />
) : (
<Auth />
);
}}
/>
<IonRouterOutlet>
<PrivateRoute exact path="/Dashboard/:id">
<Dashboard />
</PrivateRoute>
...
</IonRouterOutlet>
</Switch>
</IonReactRouter>
</IonApp>
);
};
Link to component extract:
return (
<ResultWrap key={i}>
<IonItem routerLink={`/Dashboard/${item.id}`}>
<SearchResult>{item.title}</SearchResult>
</IonItem>
<Type>
<TypeWrap>{item.type}</TypeWrap>
</Type>
</ResultWrap>
);
})
It seems that I was using the component incorrectly:
Wrong way:
<PrivateRoute exact path="/Dashboard/:id">
<Dashboard />
</PrivateRoute>
Right way:
<PrivateRoute exact path="/Dashboard/:id" component={Dashboard}>

Redux-Form unable to redirect after onSubmit Success

I would like to redirect to another page after a successful submit in redux-form.
I have tried the follow but the redirect either fails or doesn't do anything
REACT-ROUTER-DOM:
This results is an error 'TypeError: Cannot read property 'push' of undefined'
import { withRouter } from "react-router-dom";
FacilityComplianceEditForm = withRouter(connect(mapStateToProps (FacilityComplianceEditForm));
export default reduxForm({
form: "FacilityComplianceEditForm",
enableReinitialize: true,
keepDirtyOnReinitialize: true,
onSubmitSuccess: (result, dispatch, props) => {
props.history.push('/facilities') }
})(FacilityComplianceEditForm);
REACT-ROUTER-REDUX:
This submits successfully, the data is saved to the DB, but the page does not redirect.
import { push } from "react-router-redux";
export default reduxForm({
form: "FacilityComplianceEditForm",
enableReinitialize: true,
keepDirtyOnReinitialize: true,
onSubmitSuccess: (result, dispatch, props) => { dispatch(push('/facilities')) }
})(FacilityComplianceEditForm);
I also tried onSubmitSuccess: (result, dispatch, props) => dispatch(push('/facilities')) without the {} around dispatch statement but it didn't work
APP.JS to show the path does exist
class App extends Component {
render() {
return (
<div>
<Header />
<div className="container-fluid">
<Switch>
<Route exact path="/facilities" render={() => <FacilitySearch {...this.props} />} />
<Route exact path="/facilities/:id" render={(props) => <FacilityInfo id={props.match.params.id} {...this.props} />} />
<Route exact path="/facilities/compliance/:id" render={(props) => <FacilityComplianceEditForm id={props.match.params.id}{...this.props} />
<Redirect from="/" exact to="/facilities" />
<Redirect to="/not-found" />
</Switch>
</div>
<Footer />
</div>
);
}
}
export default App;
REDUCER:
export const complianceByIdReducer = (state = INTIAL_STATE.compId, action) => {
switch (action.type) {
console.log(state, action)
case "CREATE_NEW_COMPLIANCE":
return {
...state,
compId: action.compCreate
}
default:
return state
}
}
ACTION:
export const createCompliance = (id, compObj) => {
return dispatch => {
axios.post("/api/facilities/compliance/" + id, compObj)
.then(res => { return res.data })
.then(compCreate => {
dispatch(createComplianceSuccess(compCreate));
alert("New compliance created successfully") //this does get triggered
})
}
}
const createComplianceSuccess = compCreate => {
return {
type: "CREATE_NEW_COMPLIANCE",
compCreate: compCreate
}
}
REDIRECT OBJECT RETURNED FROM SUBMIT SUCCESS
STORE
import * as redux from "redux";
import thunk from "redux-thunk";
import {
facilityListReducer,
facilityReducer,
facilityLocationReducer,
facilityHistoricalNameReducer,
facilityComplianceReducer,
complianceByIdReducer
} from "../reducers/FacilityReducers";
import { projectFormsReducer } from "../reducers/FormsReducers";
import { errorReducer } from "../reducers/ErrorReducer";
import { reducer as formReducer } from "redux-form";
export const init = () => {
const reducer = redux.combineReducers({
facilityList: facilityListReducer,
facility: facilityReducer,
facilityLocation: facilityLocationReducer,
historicalNames: facilityHistoricalNameReducer,
facilityCompliance: facilityComplianceReducer,
compId: complianceByIdReducer,
countyList: projectFormsReducer,
errors: errorReducer,
form: formReducer
});
const store = redux.createStore(reducer, redux.applyMiddleware(thunk));
return store;
};
react-router-redux is deprecated so I did not want to add it to my project. I followed this post and made some modification to how routing was set up and it now works.