🚀 Big News: Socket Acquires Coana to Bring Reachability Analysis to Every Appsec Team.Learn more
Socket
Sign inDemoInstall
Socket

redux-auth-wrapper

Package Overview
Dependencies
Maintainers
1
Versions
26
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

redux-auth-wrapper - npm Package Compare versions

Comparing version

to
2.0.0-beta3

authWrapper.js

29

package.json
{
"name": "redux-auth-wrapper",
"version": "2.0.0-beta2",
"version": "2.0.0-beta3",
"description": "A utility library for handling authentication and authorization for redux and react-router",
"files": [
"*.md",
"LICENSE",
"lib",
"src"
],
"scripts": {
"build": "mkdir -p lib && babel ./src --out-dir ./lib",
"build": "mkdirp lib && babel ./src --out-dir ./lib",
"build:clean": "rimraf ./lib",
"build:copyFiles": "cp -rf package.json LICENSE.txt README.md lib/.",
"dist": "cd lib && npm publish",
"dist:prepare": "npm run build:clean && npm run build && npm run build:copyFiles",
"lint": "eslint src test",
"prepublish": "rm -rf lib && npm run build",
"test": "mocha --compilers js:babel-core/register --recursive --require test/init.js",
"test:cov": "babel-node --max-old-space-size=4076 $(npm bin)/babel-istanbul cover $(npm bin)/_mocha -- --require test/init.js",
"test:watch": "mocha --compilers js:babel-core/register --recursive --require test/init.js -w"
"test": "mocha --compilers js:babel-core/register --recursive --require test/init.js test/authWrapper-test.js",
"test:cov": "babel-node --max-old-space-size=4076 $(npm bin)/babel-istanbul cover $(npm bin)/_mocha -- --require test/init.js test/authWrapper-test.js",
"test:watch": "mocha --compilers js:babel-core/register --recursive --require test/init.js -w test/authWrapper-test.js",
"docs:clean": "rimraf _book",
"docs:prepare": "gitbook install",
"docs:build": "npm run docs:prepare && gitbook build -g redux-saga/redux-saga",
"docs:watch": "npm run docs:prepare && gitbook serve",
"docs:publish": "npm run docs:clean && npm run docs:build && cp README.md _book && cd _book && git init && git commit --allow-empty -m 'update book' && git checkout -b gh-pages && touch .nojekyll && git add . && git commit -am 'update book' && git push git@github.com:mjrussell/redux-auth-wrapper gh-pages --force"
},

@@ -45,4 +47,6 @@ "repository": {

"expect": "1.20.2",
"gitbook-cli": "2.3.0",
"jsdom": "9.8.3",
"lodash": "4.16.6",
"mkdirp": "0.5.1",
"mocha": "3.1.2",

@@ -54,2 +58,3 @@ "react": "15.3.2",

"redux": "3.6.0",
"rimraf": "2.6.1",
"sinon": "1.17.6",

@@ -56,0 +61,0 @@ "webpack": "1.13.3"

@@ -11,469 +11,24 @@ # redux-auth-wrapper

`npm install --save redux-auth-wrapper`
`npm install --save redux-auth-wrapper@2.0-beta`
**Versioning Note:** This project adheres to [semver](http://semver.org/). Please view the [Changelog](/CHANGELOG.md) for release notes.
redux-auth-wrapper is a utility library for handling authentication and authorization in react + redux applications.
## Contents
* [Motivation](#motivation)
* [Tutorial](#tutorial)
* [API](#api)
* [Authorization & Advanced Usage](#authorization--advanced-usage)
* [Hiding and Alternate Components](#hiding-and-alternate-components)
* [Where to define & apply the wrappers](#where-to-define--apply-the-wrappers)
* [Protecting Multiple Routes](#protecting-multiple-routes)
* [Dispatching an Additional Redux Action on Redirect](#dispatching-an-additional-redux-action-on-redirect)
* [Server Side Rendering](#server-side-rendering)
* [React Native](#react-native)
* [Examples](#examples)
Read the documentation at http://mjrussell.github.io/redux-auth-wrapper
## Motivation
## Version 2
At first, handling authentication and authorization seems easy in React-Router and Redux. After all, we have a handy [onEnter](https://github.com/rackt/react-router/blob/master/docs/API.md#onenternextstate-replace-callback) method, shouldn't we use it?
Version 2.x is a big internal rewrite! It provides a massive increase in flexibility when using redux-auth-wrapper and also introduces some breaking changes. See the [Migration Guide](http://mjrussell.github.io/redux-auth-wrapper/docs/Migrating.html) for more details if coming from 1.x. Or check out the [Getting Started](https://mjrussell.github.io/redux-auth-wrapper/docs/Getting-Started/Overview.html) guide if you've never used redux-auth-wrapper before.
`onEnter` is great, and useful in certain situations. However, here are some common authentication and authorization problems `onEnter` does not solve:
* Decide authentication/authorization from redux store data (there are some [workarounds](https://github.com/CrocoDillon/universal-react-redux-boilerplate/blob/master/src/routes.js#L8))
* Recheck authentication/authorization if the store updates (but not the current route)
* Recheck authentication/authorization if a child route changes underneath the protected route (React Router 2.0 now supports this with `onChange`)
Looking for Version 1.x? You can browse the 1.x REDME [here](https://github.com/mjrussell/redux-auth-wrapper/tree/1.x).
An alternative approach is to use [Higher Order Components](https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750#.ao9jjxx89).
> A higher-order component is just a function that takes an existing component and returns another component that wraps it
## Submitting Issues
Redux-auth-wrapper provides higher-order components for easy to read and apply authentication and authorization constraints for your components.
Having trouble? First check out the [Troubleshooting](https://mjrussell.github.io/redux-auth-wrapper/docs/Troubleshooting.html) section of the documentation, and then search the issues, both open and closed for your problem. If you are still having trouble or have a question on using redux-auth-wrapper, please open an issue! You can also ask on the gitter channel.
## Tutorial
## Examples
* [React Router 3](https://github.com/mjrussell/redux-auth-wrapper/tree/master/examples/react-router-3)
* [React Router 4](https://github.com/mjrussell/redux-auth-wrapper/tree/master/examples/react-router-4)
Usage with [React-Router-Redux](https://github.com/rackt/react-router-redux) (Version 4.0)
```js
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore, combineReducers, applyMiddleware, compose } from 'redux'
import { Provider } from 'react-redux'
import { Router, Route, browserHistory } from 'react-router'
import { routerReducer, syncHistoryWithStore, routerActions, routerMiddleware } from 'react-router-redux'
import { UserAuthWrapper } from 'redux-auth-wrapper'
import userReducer from '<project-path>/reducers/userReducer'
const reducer = combineReducers({
routing: routerReducer,
user: userReducer
})
const routingMiddleware = routerMiddleware(browserHistory)
// Note: passing middleware as the last argument requires redux@>=3.1.0
const store = createStore(
reducer,
applyMiddleware(routingMiddleware)
)
const history = syncHistoryWithStore(browserHistory, store)
// Redirects to /login by default
const UserIsAuthenticated = UserAuthWrapper({
authSelector: state => state.user, // how to get the user state
redirectAction: routerActions.replace, // the redux action to dispatch for redirect
wrapperDisplayName: 'UserIsAuthenticated' // a nice name for this auth check
})
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<Route path="/" component={App}>
<Route path="login" component={Login}/>
<Route path="foo" component={UserIsAuthenticated(Foo)}/>
<Route path="bar" component={Bar}/>
</Route>
</Router>
</Provider>,
document.getElementById('mount')
)
```
And your userReducer looks something like:
```js
const userReducer = (state = {}, { type, payload }) => {
if (type === USER_LOGGED_IN) {
return payload
}
if (type === USER_LOGGED_OUT) {
return {}
}
return state
}
```
When the user navigates to `/foo`, one of the following occurs:
1. If The user data is null or an empty object:
The user is redirected to `/login?redirect=%2foo`
*Notice the url contains the query parameter `redirect` for sending the user back to after you log them into your app*
2. Otherwise:
The `<Foo>` component is rendered and passed the user data as a property
Any time the user data changes, the UserAuthWrapper will re-check for authentication.
**Note:** You still need to provide a mechanism for redirecting the user from the login page back to your component. You can
also do that with redux-auth-wrapper! See the [loading example](https://github.com/mjrussell/redux-auth-wrapper/blob/master/examples/loading/app.js) for further details.
## API
`UserAuthWrapper(configObject)(DecoratedComponent)`
#### Config Object Keys
* `authSelector(state, [ownProps]): authData` \(*Function*): A state selector for the auth data. Just like `mapToStateProps`.
ownProps will be null if isOnEnter is true because onEnter hooks cannot receive the component properties. Can be ignored when not using onEnter.
* `authenticatingSelector(state, [ownProps]): Bool` \(*Function*): A state selector indicating if the user is currently authenticating. Just like `mapToStateProps`. Useful for async session loading.
* `LoadingComponent` \(*Component*): A React component to render while `authenticatingSelector` is `true`. Will be passed
all properties passed into the wrapped component, including `children`.
* `FailureComponent` \(*Component*): A React component to render when `authenticatingSelector` is `false`. If specified, the wrapper will
**not** redirect. Can be set to `null` to display nothing when the user is not authenticated/authorized.
* `[failureRedirectPath]` \(*String | (state, [ownProps]): String*): Optional path to redirect the browser to on a failed check. Defaults to `/login`. Can also be a function of state and ownProps that returns a string.
* `[redirectQueryParamName]` \(*String*): Optional name of the query parameter added when `allowRedirectBack` is true. Defaults to `redirect`.
* `[redirectAction]` \(*Function*): Optional redux action creator for redirecting the user. If not present, will use React-Router's router context to perform the transition.
* `[wrapperDisplayName]` \(*String*): Optional name describing this authentication or authorization check.
It will display in React-devtools. Defaults to `UserAuthWrapper`
* `[predicate(authData): Bool]` \(*Function*): Optional function to be passed the result of the `authSelector` param.
If it evaluates to false the browser will be redirected to `failureRedirectPath`, otherwise `DecoratedComponent` will be rendered. By default, it returns false if `authData` is {} or null.
* `[allowRedirectBack]` \(*Bool | (location, redirectPath): Bool* ): Optional bool on whether to pass a `redirect` query parameter to the `failureRedirectPath`. Can also be a function of location and the computed `failureRedirectPath` passed above, that must return a boolean value. Defaults to `true`.
* `[propMapper]` \(*Function*): Optional function that takes the props passed into the wrapped component and returns those props to pass to the DecoratedComponent, The LoadingComponent, and the FailureComponent.
#### Returns
After applying the configObject, `UserAuthWrapper` returns a function which can applied to a Component to wrap in authentication and
authorization checks. The function also has the following extra properties:
* `onEnter(store, nextState, replace)` \(*Function*): Function to be optionally used in the [onEnter](https://github.com/reactjs/react-router/blob/master/docs/API.md#onenternextstate-replace-callback) property of a route.
#### Component Parameter
* `DecoratedComponent` \(*React Component*): The component to be wrapped in the auth check. It will pass down all props given to the returned component as well as the prop `authData` which is the result of the `authSelector`.
The component is not modified and all static properties are hoisted to the returned component
## Authorization & Advanced Usage
```js
/* Allow only users with first name Bob */
const OnlyBob = UserAuthWrapper({
authSelector: state => state.user,
redirectAction: routerActions.replace,
failureRedirectPath: '/app',
wrapperDisplayName: 'UserIsOnlyBob',
predicate: user => user.firstName === 'Bob'
})
/* Admins only */
// Take the regular authentication & redirect to login from before
const UserIsAuthenticated = UserAuthWrapper({
authSelector: state => state.user,
redirectAction: routerActions.replace,
wrapperDisplayName: 'UserIsAuthenticated'
})
// Admin Authorization, redirects non-admins to /app and don't send a redirect param
const UserIsAdmin = UserAuthWrapper({
authSelector: state => state.user,
redirectAction: routerActions.replace,
failureRedirectPath: '/app',
wrapperDisplayName: 'UserIsAdmin',
predicate: user => user.isAdmin,
allowRedirectBack: false
})
// Now to secure the component: first check if the user is authenticated, and then check if the user is an admin
<Route path="foo" component={UserIsAuthenticated(UserIsAdmin(Admin))}/>
```
The ordering of the nested higher order components is important because `UserIsAuthenticated(UserIsAdmin(Admin))`
means that logged out admins will be redirected to `/login` before checking if they are an admin.
Otherwise admins would be sent to `/app` if they weren't logged in and then redirected to `/login`, only to find themselves at `/app`
after entering their credentials.
## Hiding and Alternate Components
#### Hiding Components
The auth wrappers can be used for more than redirection. You can use the `FailureComponent` parameter to hide a
component or display an alternative component when the user is not authorized. Keep in mind that wrappers that use
`FailureComponent` will not redirect users.
Here is an example that hides a link from a non-admin user.
```js
const VisibleOnlyAdmin = UserAuthWrapper({
authSelector: state => state.user,
wrapperDisplayName: 'VisibleOnlyAdmin',
predicate: user => user.isAdmin,
FailureComponent: null
})
// Applying to a function component for simplicity but could be Class or createClass component
const AdminOnlyLink = VisibleOnlyAdmin(() => <Link to='/admin'>Admin Section</Link>)
```
You can see an example of hiding links in the basic example.
#### Alternate Components
Alternatively, you can specify a FailureComponent to display an alternative component, in this example we specify a new
function for our returned HOC to make it more flexible to apply across the app.
```js
const AdminOrElse = (Component, FailureComponent) => UserAuthWrapper({
authSelector: state => state.user,
wrapperDisplayName: 'AdminOrElse',
predicate: user => user.isAdmin,
FailureComponent
})(Component)
// Show Admin dashboard to admins and user dashboard to regular users
<Route path='/dashboard' component={AdminOrElse(AdminDashboard, UserDashboard)} />
```
## Where to define & apply the wrappers
One benefit of the beginning example is that it is clear from looking at the Routes where the
authentication & authorization logic is applied. There are a variety of other places to apply redux-auth-wrapper. Please
review this section first to avoid incorrectly applying the HOC and causing bugs in your code.
#### Safe to Apply
Directly inside ReactDOM.render:
```js
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<Route path="/" component={App}>
<Route path="auth" component={UserIsAuthenticated(Foo)}/>
...
</Route>
</Router>
</Provider>,
document.getElementById('mount')
)
```
Separate route config file:
```js
const routes = (
<Route path="/" component={App}>
<Route path="auth" component={UserIsAuthenticated(Foo)}/>
...
</Route>
)
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
{routes}
</Router>
</Provider>,
document.getElementById('mount')
)
```
Applied in the component file (es7):
```js
@UserIsAuthenticated
export default class MyComponent extends Component {
...
}
```
Applied in the component file (es6):
```js
class MyComponent extends Component {
...
}
export default UserIsAuthenticated(MyComponent)
```
Applied outside the component file:
```js
import MyComponent from './component/mycomponent.js'
const MyAuthComponent = UserIsAuthenticated(MyComponent)
```
#### Not Safe to Apply
The following are all not safe because they create a new component over and over again, preventing react from considering these the "same" component and causing mounting/unmounting loops.
Inside of render:
```js
import MyComponent from './component/MyComponent.js'
class MyParentComponent extends Component {
render() {
const MyAuthComponent = UserIsAuthenticated(MyComponent)
return <MyAuthComponent />
}
}
```
Inside of any `getComponent`:
```js
const routes = (
<Route path="/" component={App}>
<Route path="auth" getComponent={(nextState, cb) => {
cb(null, UserIsAuthenticated(Foo))
}} />
...
</Route>
)
```
## Protecting Multiple Routes
Because routes in React Router are not required to have paths, you can use nesting to protect multiple routes without applying
the wrapper multiple times.
```js
const Authenticated = UserIsAuthenticated((props) => React.cloneElement(props.children, props));
<Route path='/' component={App}>
<IndexRedirect to="/login" />
<Route path='login' component={Login} />
<Route component={Authenticated}>
<Route path="foo" component={Foo} />
<Route path="bar" component={Bar} />
</Route>
</Route>
```
## Dispatching an Additional Redux Action on Redirect
You may want to dispatch an additional redux action when a redirect occurs. One example of this is to display a notification message
that the user is being redirected or don't have access to that protected resource. To do this, you can chain the `redirectAction`
parameter using `redux-thunk` middleware. It depends slightly on if you are using a redux + routing solution or just React Router.
#### Using `react-router-redux` or `redux-router` and dispatching an extra redux action in the wrapper
```js
import { replace } from 'react-router-redux'; // Or your redux-router equivalent
import addNotification from './notificationActions';
// Admin Authorization, redirects non-admins to /app
const UserIsAdmin = UserAuthWrapper({
failureRedirectPath: '/app',
predicate: user => user.isAdmin,
redirectAction: (newLoc) => (dispatch) => {
dispatch(replace(newLoc));
dispatch(addNotification({ message: 'Sorry, you are not an administrator' }));
},
...
})
```
#### Using React Router with history singleton and extra redux action
```js
import { browserHistory } from 'react-router';
import addNotification from './notificationActions';
// Admin Authorization, redirects non-admins to /app
const UserIsAdmin = UserAuthWrapper({
failureRedirectPath: '/app',
predicate: user => user.isAdmin,
redirectAction: (newLoc) => (dispatch) => {
browserHistory.replace(newLoc);
dispatch(addNotification({ message: 'Sorry, you are not an administrator' }));
},
...
})
```
## Server Side Rendering
If your `UserAuthWrapper` uses redirection, then you may need to use the `onEnter` property
of a `<Route>` to perform authentication and authorization checks for Server Side Rendering. (Note: If you are only using `FailureComponent` and not redirecting in your `UserAuthWrapper`, then you do not need to use `onEnter` option described below.)
During onEnter, selectors such as `authSelector`, `authenticatingSelector`, and `failureRedirectPath` (if you are using)
the function variation, will receive react-router's `nextState` as their second argument instead of the component props.
You can access the `onEnter` method of the `UserAuthWrapper` after applying the config parameters:
```js
import { UserAuthWrapper } from 'redux-auth-wrapper';
const UserIsAuthenticated = UserAuthWrapper({
authSelector: state => state.user,
redirectAction: routerActions.replace,
wrapperDisplayName: 'UserIsAuthenticated'
})
const getRoutes = (store) => {
const connect = (fn) => (nextState, replaceState) => fn(store, nextState, replaceState);
return (
<Route>
<Route path="/" component={App}>
<Route path="login" component={Login}/>
<Route path="foo" component={UserIsAuthenticated(Foo)} onEnter={connect(UserIsAuthenticated.onEnter)} />
</Route>
</Route>
);
};
```
#### Server Side Rendering (SSR) with nested auth wrappers
To implement SSR with nested wrappers, you will have to provide a function to chain `onEnter` functions of each wrapper. To illustrate this, we can modify the example provided in the [Authorization & Advanced Usage](#authorization--advanced-usage) section above, wherein `UserIsAuthenticated` is the parent wrapper and `UserIsAdmin` is the child wrapper.
```js
import { UserAuthWrapper } from 'redux-auth-wrapper';
const UserIsAuthenticated = UserAuthWrapper({
authSelector: state => state.user,
redirectAction: routerActions.replace,
wrapperDisplayName: 'UserIsAuthenticated'
})
// Admin Authorization, redirects non-admins to /app and don't send a redirect param
const UserIsAdmin = UserAuthWrapper({
authSelector: state => state.user,
redirectAction: routerActions.replace,
failureRedirectPath: '/app',
wrapperDisplayName: 'UserIsAdmin',
predicate: user => user.isAdmin,
allowRedirectBack: false
})
const getRoutes = (store) => {
const connect = (fn) => (nextState, replaceState) => fn(store, nextState, replaceState);
//This executes the parent onEnter first, going from left to right.
// `replace` has to be wrapped because we want to stop executing `onEnter` hooks
// after the first call to `replace`.
const onEnterChain = (...listOfOnEnters) => (store, nextState, replace) => {
let redirected = false;
const wrappedReplace = (...args) => {
replace(...args);
redirected = true;
};
listOfOnEnters.forEach((onEnter) => {
if (!redirected) {
onEnter(store, nextState, wrappedReplace);
}
});
};
return (
<Route>
<Route path="/" component={App}>
<Route path="login" component={Login}/>
<Route path="foo"
component={UserIsAuthenticated(UserIsAdmin(Admin))}
onEnter={connect(onEnterChain(UserIsAuthenticated.onEnter, UserIsAdmin.onEnter))} />
</Route>
</Route>
);
};
```
## React Native
This library can be used with React Native `>= 0.25.0` without any changes.
Using React Native and redux-auth-wrapper? Please help create an example for others to get started!
## Examples
* [Basic Example](https://github.com/mjrussell/redux-auth-wrapper/tree/master/examples/basic)
* [Loading Component Example](https://github.com/mjrussell/redux-auth-wrapper/tree/master/examples/loading)
* [Local Storage Example](https://github.com/mjrussell/redux-auth-wrapper/tree/master/examples/localStorage)
Other examples not yet updated to v2:
* [Redux-Router and React-Router 1.0 with JWT](https://github.com/mjrussell/react-redux-jwt-auth-example/tree/auth-wrapper)
* [React-Router-Redux and React-Router 2.0 with JWT](https://github.com/mjrussell/react-redux-jwt-auth-example/tree/react-router-redux)