A Guide To setup authentication in react typescript and redux

A Guide To setup authentication in react typescript and redux

From this tutorial, we will create authentication system by creating private and guest routes with implementation of redux NOTE: For understanding this ,you must have basic knowledge in react redux and its flow

What is Redux?

1.Redux is a state management container for JavaScript applications. 2.Redux makes it easy to manage the state of your application,it helps you manage the data you display and how you respond to user actions. 3.There are 3 aspects of data that we’d need to manage in our applications: a. Fetching and storing data b. Assigning data to UI elements c. Changing data

4.There are 3 main parameter in redux.

a. Actions: Actions are payloads of information that send data from your application to your store. They are the source of information for the store. We can send data to the store using store.dispatch().

b. Reducers: The Reducers specify how our application’s state changes in response to actions that are sent to the store.

c. Stores: The Store is the object that bring and combine Actions & Reducers together. The store has the following responsibilities:

  • Holds the application state;
  • Allows access to state via getState();
  • Allows state to be updated via dispatch(action);
  • Registers listeners via subscribe(listener);
  • Handles unregistering of listeners via the function returned by subscribe(listener).

Environment setup

At First ,all you need have installed react with typescript for creating our application , You can install it using npm or yarn using terminal:

Step1:$ npx create-react-app my-app — template typescript
 or
yarn create react-app my-app — template typescript
Step2:install redux as follow
$ npm install — save redux react-redux redux-thunk
Step3:install axios for api call
$ npm install --save axios
Step4:install react-router-dom
$ npm install --save react-router-dom

5.Now inside your app folder make 3 folder as show in below image ->components//for app component ->redux//for redux stuff ->utils//for extra helper functions

image.png

Now inside redux folder we will create 2 folders “actions” and “reducers” and will create stores.ts and types.ts file as follow

image.png

//In types.ts we will define constants for user authentication and UI loading purpose

//user reducer types
export const SET_AUTHENTICATED=’SET_AUTHENTICATED’;
export const SET_UNAUTHENTICATED=’SET_UNAUTHENTICATED’;
export const SET_USER=’SET_USER’;
export const LOADING_USER=’LOADING_USER’;
//UI reducer types
export const SET_ERRORS=’SET_ERRORS’;
export const LOADING_UI=’LOADING_UI’;
export const CLEAR_ERRORS=’CLEAR_ERRORS’;

In “actions” folder we will create userActions.ts file and in “reducers” folder we create userReducers.ts & uiReducers.ts

image.png

//in useActions.ts file
import { SET_USER, SET_ERRORS, LOADING_UI, CLEAR_ERRORS, SET_UNAUTHENTICATED, LOADING_USER } from ‘../types’
import axios from ‘axios’;
export const loginUser = (userData: any, history: any) => (dispatch: any) => {
 dispatch({ type: LOADING_UI })
axios.post(‘login’, userData)
 .then((res) => {
 const token = `Bearer ${res.data.token}`;
 localStorage.setItem(‘token’, `Bearer ${res.data.token}`);//setting token to local storage
 axios.defaults.headers.common[‘Authorization’] = token;//setting authorize token to header in axios
 dispatch(getUserData());
 dispatch({ type: CLEAR_ERRORS });
 console.log(‘success’);
 history.push(‘/’);//redirecting to index page after login success
})
 .catch((err) => {
 console.log(err);
 dispatch({
 type: SET_ERRORS,
 payload: err.response.data
});
});
}
//for fetching authenticated user information
export const getUserData = () => (dispatch: any) => {
 dispatch({ type: LOADING_USER });
 axios.get(‘/user’)
 .then(res => {
 console.log(‘user data’, res.data);
dispatch({
 type: SET_USER,
 payload: res.data
});
}).catch(err => {
 console.log(err);
});
}
export const logoutUser = () => (dispatch: any) => {
 localStorage.removeItem(‘token’);
 delete axios.defaults.headers.common[‘Authorization’]
 dispatch({
 type: SET_UNAUTHENTICATED
});
 window.location.href = ‘/login’;//redirect to login page
};
NOTE:your server must return response like as follow for getting the authenticated user information getUserData function must return response as
{
 “credentials”: {
 “createdAt”: “20200130T10:29:44.898Z”,
 “email”: “bikash@gmail.com”,
 “userId”: “D4hCBB4RcAdTjawNCQ0K4ItED563”
 }
}
loginUser function must return token after successful login as:
{
    "token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjI1OTc0MmQyNjlhY2IzNWZiNjU3YzBjNGRkMmM3YjcyYWEzMTRiNTAiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vcGFya2l0LTI3YTQ4IiwiYXVkIjoicGFya2l0LTI3YTQ4IiwiYXV0aF90aW1lIjoxNTgwNjM3MzgyLCJ1c2VyX2lkIjoiRDRoQ0JCNFJjQWRUamF3TkNRMEs0SXRFRDU2MyIsInN1YiI6IkQ0aENCQjRSY0FkVGphd05DUTBLNEl0RUQ1NjMiLCJpYXQiOjE1ODA2MzczODIsImV4cCI6MTU4MDY0MDk4MiwiZW1haWwiOiJwYWRhbTEyQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJlbWFpbCI6WyJwYWRhbTEyQGdtYWlsLmNvbSJdfSwic2lnbl9pbl9wcm92aWRlciI6InBhc3N3b3JkIn19.M0hTKqNb_0_3qmqS7xiI2I3mDpm6_gaAEIzRBydiu89pzaBJPXZ1BngdLQeJ2_JOh6GJBHDDeX52yM-DZdHoK6cC93ugqMjz7bzegf5YJzdYEtBPHFKvaDUQEgzgGhGbU_jl5xL09uIixPgOChw3zWbVAbvj4ZjJrT4u5aC96txhDA9gMPtRLq-9VoL4SQTRjOc-_vP08SylWP1oklMgsCfEGFCk81zcEsGEHfHwH_dIcj208G483lzD29Ghqmo3nKGp6u3eeXLxoyop8hKQsJUbvJK9zZmobfqZfghuDgiMj5_LqImmTeQR6jw7_S1NK0uDMcQl3G0lxezvS0n53w"
}
//in userReducer.ts
import { SET_USER, SET_AUTHENTICATED, SET_UNAUTHENTICATED, LOADING_USER } from ‘../types’
const initialState = {
 authenticated: false,
 credentials: {},
 loading: false
}

export default function (state = initialState, action:any) {
 switch (action.type) {
 case SET_AUTHENTICATED:
 return {
 …state,
 authenticated: true
 };
 case SET_UNAUTHENTICATED:
 return initialState;
 case SET_USER:
 return {
 authenticated: true,
 loading:false,
 …action.payload
 };
case LOADING_USER:
 return {
 …state,
 loading: true
 };
 default:
 return state;
}
}
//in uiReducer.ts
import { SET_ERRORS, LOADING_UI, CLEAR_ERRORS } from ‘../types’
const initialState = {
 loading: false,
 errors: null
}
export default function (state = initialState, action:any) {
switch (action.type) {
 case SET_ERRORS:
 return {
 …state,
 loading: false,
 errors: action.payload
 };
 case CLEAR_ERRORS:
 return {
 …state,
 loading: false,
 errors: null
};
 case LOADING_UI:
 return {
 …state,
 loading: true
 }
 default:
 return state;
}
}

//in store.ts
import { createStore, combineReducers, applyMiddleware, compose } from ‘redux’
import thunk from ‘redux-thunk’
import userReducer from ‘./reducers/userReducer’
import uiReducer from ‘./reducers/uiReducer’
const initialState = {};
const middleware = [thunk];
//this is for redux devtool purpose
declare global {
interface Window {
 __REDUX_DEVTOOLS_EXTENSION__?: typeof compose;
}
}
const reducer = combineReducers({
 user: userReducer,//user key ma store gareko
 UI: uiReducer
});
const store = createStore(reducer, initialState, compose(applyMiddleware(…middleware), (window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()) as any));
export default store;

Now inside components folder we will create 2 functional components Login.tsx,Index.tsx NOTE:Login.tsx is our guest component that doesn;t need to be secure with authentication. We will use material UI for login page.

// with npm
npm install @material-ui/core

// with yarn
yarn add @material-ui/core

Index.tsx is our private component that need to be secure and only authenticated user can access it

//in Login.tsx inside components
import React, { useState, useEffect } from “react”;
import CssBaseline from ‘@material-ui/core/CssBaseline’;
import TextField from ‘@material-ui/core/TextField’;
import FormControlLabel from ‘@material-ui/core/FormControlLabel’;
import Checkbox from ‘@material-ui/core/Checkbox’;
import Grid from ‘@material-ui/core/Grid’;
import Typography from ‘@material-ui/core/Typography’;
import { makeStyles } from ‘@material-ui/core/styles’;
import Container from ‘@material-ui/core/Container’;
import { Paper, Box } from ‘@material-ui/core’;
import Button from ‘@material-ui/core/Button’;
import { Link, useHistory, useLocation } from ‘react-router-dom’
import CircularProgress from ‘@material-ui/core/CircularProgress’;
//redux stuff
import { connect } from ‘react-redux’;
import { loginUser } from ‘../../redux/actions/userActions’
function Login(props: any) {
const [values, setValues] = useState({
 email: “”,
 password: “”
 } as userDataProps);
 const [errors, setErrors] = useState({} as formError);
 const [loading, setLoading] = useState(false);
useEffect(() => {
 if (props.UI.errors) {
 setErrors(props.UI.errors);
}
 setLoading(props.UI.loading);
}, [props.UI])
const handleSubmit = (e: any) => {
 e.preventDefault();
 setLoading(true);
//your client side validation here
//after success validation
 const userData = {
 email: values.email,
 password: values.password,
};
 props.loginUser(userData, props.history);
 }
const handleChange = (e: any) => {
 e.persist();
 setValues(values => ({
 …values,
 [e.target.name]: e.target.value
 }));
 };
return (
 <Box>
 <Box className={classes.box}>
 <Typography variant=”h4">
 <Box fontWeight={600} letterSpacing={3}>
 SIGN IN
 </Box>
 </Typography>
 </Box>
 <Container
 component=”main”

 maxWidth=”md”>
 <CssBaseline />
 <Grid
 container
 alignContent=”center”
 alignItems=”center”
 justify=”center”

 spacing={3}>
 <Grid
 item
 md={3}>
 <img src={your_logo_here} alt=”My Logo” />
 </Grid>
 <Grid item md={9}>
 <Paper>
 <Box>
 <TextField
 variant=”outlined”
 margin=”none”
 value={values.email}
 fullWidth
 id=”email”
 label=”Email Addressname=”email”
 type=”email”
 onChange={handleChange}
 helperText={errors.email}
 error={errors.email ? true : false}
 />
 <TextField
 variant=”outlined”
 margin=”normal”
 value={values.password}
 fullWidth
 name=”password”
 label=”Password”
 type=”password”
 onChange={handleChange}
 helperText={errors.password}
 error={errors.password ? true : false}
 />
 {errors.message && (
 <Typography variant=”body2">
 {errors.message}
 </Typography>
 )}
 <Grid container>
 <Grid item sm={6} md={6}>
 <FormControlLabel

 control={
 <Checkbox value=”remember” color=”primary” />
 }
 label=”Remember me”
 />
 </Grid>
 <Grid item sm={6} md={6}>
 <Link to=”#”>
 Forgot password?
 </Link>
 </Grid>
 </Grid>
 <Button type=”submit” variant=”contained” color=”primary” disabled={loading}>
 Login
 {loading && (<CircularProgress className={classes.loader} size={30} color=”secondary” />)}
 </Button>

 </Box>
 </Paper>
 </Grid>
 </Grid>
 </Container >
 </Box>
 )
}
//this map the states to our props in this functional component
const mapStateToProps = (state: any) => ({
 user: state.user,
 UI: state.UI
});
//this map actions to our props in this functional component
const mapActionsToProps = {
 loginUser
};
export default connect(mapStateToProps, mapActionsToProps)(Login)

Now we will create 3 files inside utils as follow ->GuestRoute.tsx ->PrivateRoute.tsx ->CheckAuthentication.ts

image.png

//in GuestRoute.tsx
import React from ‘react’
import { Route, Redirect,RouteProps } from ‘react-router-dom’
//redux stuff
import { connect } from ‘react-redux’
interface MyRouteProps extends RouteProps{
 component:any;
 authenticated:boolean;
 rest?:any
}
const GuestRoute: React.SFC<MyRouteProps>= ({ component: Component, authenticated, …rest }) => (
 <Route
 {…rest}
 render={(props) =>
 authenticated ?
 <Redirect to=’/’ /> :
 <Component {…props}
 />}
/>
);
const mapStateToProps = (state:any) => ({
 authenticated: state.user.authenticated
});
export default connect(mapStateToProps)(GuestRoute)

The Guest Route is for non-authenticated user.If user if already authenticated then it will redirect user to Index page

//in PrivateRoute.tsx
import React from ‘react’
import { Route, Redirect,RouteProps } from ‘react-router-dom’
import { connect } from ‘react-redux’
interface MyRouteProps extends RouteProps{
 component:any;
 authenticated:boolean;
 rest?:any
}
const PrivateRoute: React.SFC<MyRouteProps> = ({ component: Component, authenticated, …rest }:any) => (
 <Route
 {…rest}
 render={(props) =>
 authenticated ?
 <Component {…props} /> :
 <Redirect to=’/login’ />
}
/>
);
const mapStateToProps = (state:any) => ({
 authenticated: state.user.authenticated
});
export default connect(mapStateToProps)(PrivateRoute)

The PrivateRoute is only for authenticated user,if user is authenticated then only it can access to respective page otherwise it will redirect to login page

//in CheckAuthentication.ts
import jwtDecode from ‘jwt-decode’;//you must install jwt-decode using npm
import { logoutUser, getUserData } from ‘../redux/actions/userActions’
import store from ‘../redux/store’;
import axios from ‘axios’;
import { SET_AUTHENTICATED } from ‘../redux/types’
export const CheckAuthentication = () => {
 const authToken = localStorage.token;
if (authToken) {
 const decodedToken:any = jwtDecode(authToken);
 console.log(decodedToken.iss);
if (decodedToken.exp * 1000 < Date.now()) {
 store.dispatch(logoutUser());
} else {
 store.dispatch({ type: SET_AUTHENTICATED });
 axios.defaults.headers.common[‘Authorization’] = authToken;
 store.dispatch(getUserData());
 }
}
}

The CheckAuthentication functions will check the authenticated user token ,if user is authenticated then it will set SET_AUTHENTICATED with authentication status true otherwise logout the user from our

//Finally in App.tsx
import React, { useEffect } from ‘react’;
import ‘./App.css’;
import { BrowserRouter as Router, Switch, Route } from ‘react-router-dom’
import Login from ‘./components/Login.tsx’
import Index from ‘./components/Index.tsx’
import GuestRoute from ‘./utils/GuestRoute’
import PrivateRoute from ‘./utils/PrivateRoute’
//redux stuff
import { Provider } from ‘react-redux’;
import store from ‘./redux/store’;
import { CheckAuthentication } from ‘./utils/CheckAuthentication’
const App: React.FC = () => {
 useEffect(() => {
 CheckAuthentication();
}, []);
return (
 <div className=”App”>
 <Provider store={store}>
 <Router>
 <Switch>
 <PrivateRoute
 exact
 path=”/”
 component={Index} />
<GuestRoute
 exact
 path=’/login’
 component={Login} />
</Switch>
 </Router>
 </Provider>
 </div>
 )
}
export default App;