auth and views
in React.js
an intro to
all logos are copyrighted by their respective owners
all slides are licensed CC-BY-SA and code samples MIT
Paweł alxd Chojnacki
hacker and floss activist
digital nomad
full-stack JS dev with
learned my trade with
highly opinionated
batteries included (with ui-router)
created for medium and big SPAs
slow with large datasets
preferring custom magic to JS
two-way data-binding
now I work with
very little opinionated
just a V of MVC
created for components, not SPAs
blazing fast
embracing commonJS
one-way data-binding
React.js Ecosystem
react-router
import { Webpack, ES6 } from ('survive.js')
import React from ('react-tutorial')
import Router from ('react-router')
import Redux from ('redux-tutorial')
State of the App
/login
/
available with no auth token
available with an auth token
when auth token exists
when no auth token
when a request fails with 403
React tutorials explain Components
What about multi-component views?
How to implement view-specific logic?
How to authenticate React App?
React Container Components
app/
actions/
components/
config/
reducers/
requests/
stores/
views/
index.js
...are regular components
{this.props.children}
componentWillReceiveProps()
just with
and
class AuthenticatedComponent extends React.Component {
componentWillMount () {
this.checkCredentials(this.props.authToken)
}
componentWillReceiveProps (nextProps) {
this.checkCredentials(nextProps.authToken)
}
checkCredentials (authToken) {
(...)
}
render () {
return (
<div>
{this.props.children}
</div>
)
}
}
Simple Component
Pseudocode!
function requireAuthentication (Component) {
return class AuthenticatedComponent extends React.Component {
componentWillMount () {
this.checkCredentials(this.props.authToken)
}
componentWillReceiveProps (nextProps) {
this.checkCredentials(nextProps.authToken)
}
checkCredentials (authToken) {
(...)
}
render () {
return (
<div>
{this.props.authToken
? <Component {...this.props} />
: null // You can add a spinner Component here
}
</div>
)
}
}
}
Wrapper Component
Pseudocode!
import React from 'react'
import { connect } from 'react-redux'
export default function requireAuthentication (Component) {
class AuthenticatedComponent extends React.Component {
componentWillMount () {
this.checkCredentials(this.props.authToken)
}
componentWillReceiveProps (nextProps) {
this.checkCredentials(nextProps.authToken)
}
checkCredentials (authToken) {
if (!authToken) {
const storageToken = window.localStorage.getItem('authToken')
if (!storageToken) {
this.redirectToLogin()
} else {
this.props.setAuthToken(storageToken)
}
}
}
redirectToLogin () {
this.context.router.push('/login')
}
render () {
return (
<div>
{this.props.authToken
? <Component {...this.props} />
: null // You can add a spinner Component here
}
</div>
)
}
}
(...)
const mapStateToProps = (state) => ({
authToken: state.auth.token
})
const mapDispatchToProps = (dispatch) => {
return {
setAuthToken: (authToken) => {
dispatch(setAuthToken(authToken))
}
}
}
return connect(
mapStateToProps,
mapDispatchToProps
)(AuthenticatedComponent)
}
with Redux
how to use them
<Router history={browserHistory}>
<Route path='/login' component={Login} />
<Route path '/' component={authenticatedComponent}>
<IndexRoute component={Dashboard}>
</Route>
</Router>
or
<Router history={browserHistory}>
<Route path='/login' component={Login} />
<IndexRoute component={requireAuthentication(Dashboard)}>
</Router>
Pseudocode!
index.js with routing
app/index.js
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { Router, browserHistory, Route, IndexRoute } from 'react-router'
import { mainStore } from './stores/mainStore'
import requireAuthentication from './views/auth'
import Login from './views/login'
import Dashboard from './views/dashboard'
render(
<Provider store={mainStore}>
<Router history={browserHistory}>
<Route path='/login' component={Login} />
<IndexRoute component={requireAuthentication(Dashboard)}>
</Router>
</Provider>,
document.getElementById('app')
)
use single store!
const mainReducer = combineReducers({
auth,
locale,
api: combineReducers({
login,
account,
clients
}),
views: combineReducers({
progressBar,
activeWidgets
})
})
and the Redux Reducer
app/actions/auth.js
const AUTH_SET_TOKEN = 'AUTH_SET_TOKEN'
const AUTH_DELETE_TOKEN = 'AUTH_DELETE_TOKEN'
export function setAuthToken (token) {
return {
type: AUTH_SET_TOKEN,
token
}
}
export function invalidateAuthToken () {
window.localStorage.removeItem('authToken')
return {
type: AUTH_DELETE_TOKEN
}
}
app/reducers/auth.js
import {
AUTH_SET_TOKEN,
AUTH_DELETE_TOKEN
} from '../actions/auth'
export default function auth (
state = {
token: undefined
}, action
) {
switch (action.type) {
case AUTH_SET_TOKEN:
return Object.assign({}, state, {
token: action.token
})
case AUTH_DELETE_TOKEN:
return Object.assign({}, state, {
token: undefined
})
default:
return state
}
}
requests and 403s
app/requests/data.js
import fetch from 'isomorphic-fetch'
import { invalidateAuthToken } from '../actions/auth'
import config from '../config'
export function getAuthenticatedData () {
return fetch(config.request_addres, {
method: 'GET'
}).then((response) => {
if (response.status === 403) {
invalidateAuthToken()
}
return response
})
}
Redux State propagation
<Router history={browserHistory}>
<Route path='/login' component={Login} />
<Route path='/' component={requireAuthentication(Navbar)}>
<Route path='/dashboard' component={Dashboard} />
<Route path='/clients' component={Clients}>
<IndexRoute component={NewClient}>
</Route>
</Route>
</Router>
componentWillReceiveProps (nextProps) {
this.checkCredentials(nextProps.authToken)
}
Pseudocode!
redirecting from login
checkCredentials (authToken) {
const storageToken = window.localStorage.getItem('authToken')
if (authToken || storageToken) {
this.props.setAuthToken(authToken || storageToken)
this.redirectToDashboard()
}
}
import React from 'react'
import { connect } from 'react-redux'
export default function requireUnauthenticated (Component) {
class AuthenticatedComponent extends React.Component {
componentWillMount () {
this.checkCredentials(this.props.authToken)
}
componentWillReceiveProps (nextProps) {
this.checkCredentials(nextProps.authToken)
}
checkCredentials (authToken) {
const storageToken = window.localStorage.getItem('authToken')
if (authToken || storageToken) {
this.props.setAuthToken(authToken || storageToken)
this.redirectToDashboard()
}
}
redirectToDashboard () {
this.context.router.push('/dashboard')
}
render () {
return (
<div>
{this.props.authToken
? <Component {...this.props} />
: null // You can add a spinner Component here
}
</div>
)
}
}
(...)
const mapStateToProps = (state) => ({
authToken: state.auth.token
})
const mapDispatchToProps = (dispatch) => {
return {
setAuthToken: (authToken) => {
dispatch(setAuthToken(authToken))
}
}
}
return connect(
mapStateToProps,
mapDispatchToProps
)(requireUnauthenticated)
}
putting advanced view-specific logic in
container components
user permissions to component groups
auto-redirecting on 'global' level
keep auth data or user preferences
set and change locale
use Redux single store to
summing it up
views can be implemented as components
with Redux componentWillReceiveProps works wonders
use single Redux store per App
map full state, not its properties to props
it's handy to create wrappers
these slides will be available at
https://slides.com/pawelchojnacki/
auth-and-views-in-reactjs/
code samples soon at
https://alxd.org/
more on authentication at
auth and views in react.js
By Pawel Ngei
auth and views in react.js
- 2,095