redux-mad-authentication
v2.0.1
Published
Authenticating with a Spring Security application and storing the currentUser in Redux.
Downloads
82
Maintainers
Readme
About
This is 42's authentication module for Redux in combination with a specific Spring Security settings.
It can do the following things:
- Log the user in and out with your Spring application.
- Saving the current user in the Redux store.
- Send 'fetch' request with XSRF tokens and the cookies.
- Make a route only available for logged in users.
- Make a route available for specific users, based on properties of the current user.
Getting started.
We assume you have a working Redux project, if you do not yet have Redux add Redux to your project by following the Redux's instructions.
First install the following dependencies in the package.json:
- "react-redux": "5.0.3",
- "redux": "3.6.0",
- "react-router-dom": "4.0.0"
Now add the authentication-reducer to your rootReducer for example:
import { combineReducers } from 'redux';
import { authentication, AuthenticationStore } from 'redux-mad-authentication';
export interface Store {
authentication: AuthenticationStore
};
// Use ES6 object literal shorthand syntax to define the object shape
const rootReducer: Store = combineReducers({
authentication,
});
export default rootReducer;
This should add the AuthenticationStore to Redux, which will store the logged in user.
Next you have to configure the authentication module:
import { createStore } from 'redux';
import { configureAuthentication } from 'redux-mad-authentication';
export const store = createStore(
rootReducer,
);
configureAuthentication({
// The URL of your Spring back-end where the user can login (POST) and logout(DELETE)
authenticationUrl: '/api/authentication',
// The URL of your Spring back-end where the current user can be requested via GET
currentUserUrl: '/api/authentication/current',
// The route (in the front-end) the user should be redirected to when not logged in.
loginRoute: '/login',
// A reference to the dispatch function for the react store.
dispatch: store.dispatch,
// A function which returns the current 'authentication' store
authenticationStore: () => store.getState().authentication
});
The authentication module must be configured before the application is rendered.
How to
Writing a LoginForm.
In order to log the user in you must have a login form of some sorts. This library does not assume anything on how this login form should work or what it looks like.
Here's what a LoginForm should do:
- Call 'login' when the user submits the login form, with the correct body.
- Try to auto-login the user via
current
in the componentDidMount. - In the
render
Redirect the user when he is logged in.
For example:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Redirect, locationShape } from 'react-router-dom';
import { login, current } from 'redux-mad-authentication';
import { Store } from '../redux/root-reducer';
interface Props {
loggedIn: boolean,
location: locationShape
};
interface State {
username: string,
password: string,
error: boolean,
autoLoginFailed: boolean
};
export class Login extends Component<Props, State> {
state = {
username: '',
password: '',
error: false,
autoLoginFailed: false
};
// Calling `current()` automatically logs the user in when the session is still valid
componentDidMount() {
current().catch(() => {
this.setState({ autoLoginFailed: true });
});
}
onSubmit(event: Event) {
event.preventDefault();
const { username, password } = this.state;
this.setState({ error: false });
// `login` expects a body to send to the server
login({ username, password }).catch((error) => {
this.setState({ error: true });
});
}
setUsername(username: string) {
this.setState({ username });
}
setPassword(password: string) {
this.setState({ password });
}
render() {
// Be sure
if (this.props.loggedIn) {
const { from } = this.props.location.state || { from: { pathname: '/' } };
return <Redirect to={ from }/>;
}
if (this.state.autoLoginFailed === false) {
return null;
}
const { username, password, error } = this.state;
const errorMessage = error ? 'Username and password are incorrect' : '';
return (
<form>
<h1>Please log in</h1>
<p>
<label htmlFor="username">Username</label>
<input
id="username"
name="username"
type="text"
value={ username }
onChange={ (event) => this.setUsername(event.target.value) }
/>
</p>
<p>
<label htmlFor="password">Password</label>
<input
id="password"
name="password"
type="password"
value={ password }
onChange={ (event) => this.setPassword(event.target.value) }
/>
</p>
<p>{ errorMessage }</p>
<button type="sumbit" onClick={ (event) => this.onSubmit(event) }>Log in</button>
</form>
);
}
}
export default connect((store: Store) => {
return {
loggedIn: store.authentication.isLoggedIn
};
})(Login);
Writing a Logout
In order to logout you must call the 'logout' function, and make sure you Redirect the user to the login form when the user is logged in.
For example:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';
import { Store } from '.redux/root-reducer';
import { logout } from 'redux-mad-authentication';
interface Props {
isLoggedIn: boolean
};
export class Logout extends Component<Props, void> {
onLogoutClick() {
logout();
}
render() {
if (this.props.isLoggedIn === false) {
return <Redirect to="/"/>;
}
return (
<a onClick={ () => this.onLogoutClick() }>Uitloggen</a>
);
}
}
export default connect((store: Store) => {
return {
isLoggedIn: store.authentication.isLoggedIn
};
})(Logout);
Make a Route private
Some routes can only be accessible when the user is logged in. You can do this via the PrivateRoute for example:
<BrowserRouter history={ browserHistory }>
<div>
<Route exact path="/" component={ Dashboard } />
<Route path="/login" component={ Login }/>
<PrivateRoute path="/users" component={ Users } />
</div>
</BrowserRouter>
PrivateRoute works exacly like Route, except that it does not
support a render
method. You must always provide a Component
instead.
When the user tries to go to a PrivateRoute he will be redirected
to the Config's route loginRoute
.
Add authorization to a Route
Some routes can only be accessed by a type of user or a specific user. You can do this via the AuthorizedRoute.
<BrowserRouter>
<div>
<Route exact path="/" component={ Dashboard } />
<Route path="/login" component={ Login }/>
<PrivateRoute path="/users" component={ Users } />
<AuthorizedRoute
path="/pictures"
component={ Pictures }
authorizer={ (authenticationStore: AuthenticationStore) => {
return authenticationStore.currentUser.role === 'ADMIN';
}}
/>
</div>
</BrowserRouter>
The authorizer function is only ran if the user is logged in. The authorizer is given the authenticationStore as the first parameter, and is expected to return a boolean.
AuthorizedRoute works exacly like Route, except that it does not
support a render
method. You must always provide a Component
instead.
When the user tries to go to a AuthorizedRoute he will be redirected
to the Config's route loginRoute
.
Get the current user's info / login status
Lets say you have a component which must render the current user's name, you will then need the current user from the Redux store.
We can use react-redux's connect to achieve this.
For example:
import { connect } from 'react-redux';
import { Store } from '.redux/root-reducer';
function User(props) {
if (isLoggedIn) {
return <h1>Hi {{ props.currentUser.name }}</h1>
} else {
return <h1>Please log in</h1>
}
}
export default connect((store: Store) => {
return {
isLoggedIn: store.authentication.isLoggedIn,
currentUser: store.authentication.currentUser
};
})(User);
Send a request with the XSRF token as the current user.
To perform request with the XSRF token and with the cookies from the current user. You can use the 'authFetch' utility from this library:
import 'authFetch' from './authentication'
function getUser() {
authFetch('/user/1').then((response) => {
console.log(response);
});
}
authFetch
is a thin wrapper around fetch
, it only adds the
credentials and XSRF token, so it has the exact same arguments
as fetch
.