Giter VIP home page Giter VIP logo

redux-auth-wrapper's Introduction

redux-auth-wrapper

npm version Build Status Coverage Status

Decouple your Authentication and Authorization from your components!

npm install --save redux-auth-wrapper

Motivation

At first, handling authentication and authorization seems easy in React-Router and Redux. After all, we have a handy onEnter method, shouldn't we use it?

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
  • Recheck authentication/authorization if the store updates (but not the current route)
  • Recheck authentication/authorization if a child route changes underneath the protected route

An alternative approach is to use Higher Order Components.

A higher-order component is just a function that takes an existing component and returns another component that wraps it

Redux-auth-wrapper provides higher-order components for easy to read and apply authentication and authorization constraints for your components.

Tutorial

Usage with React-Router-Redux

import React from 'react'
import ReactDOM from 'react-dom'
import { createStore, combineReducers, applyMiddleware, compose } from 'redux'
import { Provider } from 'react-redux'
import { Router, Route } from 'react-router'
import { createHistory } from 'history'
import { syncReduxAndRouter, routeReducer, routeActions } from 'react-router-redux'
import { UserAuthWrapper } from 'redux-auth-wrapper'
import userReducer from '<project-path>/reducers/userReducer'

const reducer = combineReducers({
  routing: routeReducer,
  user: userReducer
})
const history = createHistory()
const routingMiddleware = syncHistory(history)

const finalCreateStore = compose(
  applyMiddleware(routingMiddleware)
)(createStore);
const store = finalCreateStore(reducer)
routingMiddleware.listenForReplays(store)

// Redirects to /login by default
const UserIsAuthenticated = UserAuthWrapper({
  authSelector: state => state.user, // how to get the user state
  redirectAction: routeActions.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:

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.

API

UserAuthWrapper(configObject)(DecoratedComponent)

Config Object Keys

  • authSelector(state, [ownProps]): authData (Function): A state selector for the auth data. Just like mapToStateProps
  • [failureRedirectPath] (String): Optional path to redirect the browser to on a failed check. Defaults to /login
  • [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 userAuthSelector param. If it evaluates to false the browser will be redirected to failureRedirectPath, otherwise DecoratedComponent will be rendered.
  • [allowRedirect] (Bool): Optional bool on whether to pass a redirect query parameter to the failureRedirectPath

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

Authorization & More Advanced Usage

/* Allow only users with first name Bob */
const OnlyBob = UserAuthWrapper({
  authSelector: state => state.user,
  redirectAction: routeActions.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: routeActions.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: routeActions.replace,
  failureRedirectPath: '/app',
  wrapperDisplayName: 'UserIsAdmin',
  predicate: user => user.isAdmin,
  allowRedirectBack: false
})

// Now to secure the component:
<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.

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.

An alternative choice might be to use es7 decorators (after turning on the proper presets) in your component:

import { UserIsAuthenticated } from '<projectpath>/auth/authWrappers';

@UserIsAuthenticated
class MyComponents extends Component {
}

redux-auth-wrapper's People

Contributors

mjrussell avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.