eazy-auth
Easy auth stuff \w redux
Battery included but extractable package to automate auth boring stuff.
The stack is redux
, react
for UI, redux-saga
for side effects and react-router-dom
for routing.
Example App
Install
yarn add eazy-auth
or
npm install eazy-auth --save
Cheat sheet
import { makeAuthReducer } from 'eazy-auth'
const rootReducer = combineReducers({
auth: makeAuthReducer(),
})
import { makeAuthFlow } from 'eazy-auth'
const { authFlow, authCall } = makeAuthFlow({
loginCall: credentials => ,
refreshTokenCall: refreshToken => ,
meCall: token => ,
})
function *mainSaga() {
yield fork(authFlow)
try {
const data = yield call(token => Api.fetchUser(token), action.payload.id)
yield put({type: "FETCH_SUCCEEDED", data})
} catch (error) {
yield put({type: "FETCH_FAILED", error})
}
const withToken = (token, baseRequest) =>
(token ? baseRequest.set('Authorization', `Bearer ${token}`) : baseRequest)
const fetchUser = token => id =>
withToken(token, request.get(`/api/users/${id}`))
}
import {
login,
logout,
clearLoginError,
updateUser,
} from 'eazy-auth'
import {
isLoginLoading,
getLoginError,
getAuthUser,
getAuthAccessToken,
getAuthRefreshToken,
} from 'eazy-auth'
import { withAuthLogin } from 'eazy-auth'
let Login = ({ handleSubmit, credentials: { email, password }, error, loading }) => (
<form onSubmit={handleSubmit}>
<div>
<input type='email' {...email} />
</div>
<div>
<input type='password' {...password} />
</div>
<div>
<input type='submit' />
</div>
{loading && <div>Login...</div>}
{error && <div>Bad credentials</div>}
</form>
)
login = withAuthLogin({
credentials: ['email', 'password'],
shouldclearErrorOnChange: true,
})(Login)
import { AuthRoute } from 'eazy-auth'
const App = () => (
<Provider store={store}>
<Router>
<Switch>
{/* Redirect user to another route if not authenticated */}
<AuthRoute
path='/profile'
exact
component={Old}
{/* Path to redirect */}
redirectTo={'/login'}
{/* Spinner to use while loading */}
spinner={null},
{/* Remeber referrer when redirect guests? */}
rememberReferrer={true}
{/* Additional function to check permission on user and redirect */}
redirectTest={user => user.age > 27 ? false : '/'}
/>
{/* Redirect user to another route if authenticated */}
<GuestRoute
path='/login'
exact
component={Login}
spinner={null}
redirectTo={'/'}
{/* Redirect to referrer if user logged in? */}
redirectToReferrer={true}
/>
{/*
Simply if user has a token in local storage and not yet me has been
performed wait until me complete before mounting component
optionally show a spinner if you want
*/}
<MaybeAuthRoute
path='/home'
exact
component={Home}
spinner={null},
/>
</Switch>
</Router>
</Provider>
)
authMiddleware
eazy-auth
was originally built with redux-saga
in mind but in certain situation you need to hook the auth "side effects" outside the redux-saga
environment for example directly in react components.
This is why the authMiddleware
was created.
Create auth middleware:
const { authFlow, authCall, makeAuthMiddleware } = makeAuthFlow({
})
const authMiddleware = makeAuthMiddleware()
const sagaMiddleware = createSagaMiddleware()
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore(
rootReducer,
undefined,
composeEnhancers(
applyMiddleware(sagaMiddleware, authMiddleware),
)
)
export const { callAuthApiObservable, callAuthApiPromise } = authMiddleware.run()
sagaMiddleware.run(mainSaga)
export default store
callAuthApiPromise(apiCall, ...args)
apiCall: (accessToken)(...args) => Promise
Curry the accessToken
if any, logout on rejection matches { status: 401|403 }
and try refresh if a refresh call is given, if refresh is good re-try the apiCall
otherwis rejects.
Return a Promise.
callAuthApiObservable(apiCall, ...args)
apiCall: (accessToken)(...args) => Promise|Observable
Same as above but implement with observables.
Return a Observable.