Role based auth for redux. Have a look here: nmpribeiro.github.io/react-role-based-auth - login with user: [email protected] pass: test_2021 on browser refresh, your sessions will persist on reload
First, setup your auth mechanism and backend.
npm install react-rb-auth
or
yarn add react-rb-auth
All library files are located inside src/lib folder.
Inside src/demo folder, you can test your library while developing.
Our Main
encapsulates all necessary Auth bootstrapping, including setting the TokenUtil
storage (this allows implementation decoupling for localStorage
in browsers or async storage
in mobile react-native apps), AuthApi
, AuthReloading
for when you are reloading the app (refresh), AuthLoading
for when your are loggin in, and other auth specific utilities.
Feel free to use ts-request-builder to make your APIs easier.
import React, { useEffect, useState } from 'react'
import { Auth, TokenUtil, RefreshApp, RBAuthErrors } from 'react-rb-auth'
import { App } from './App'
import { AuthApi } from './services/AuthApi'
import { GlobalAppApi } from './services/ExternalApi'
import { AppStorage } from './services/AppLocalStorage'
const AuthReloading: React.FC = () => (
<Spinner>
<h3>AuthReloading</h3>
</Spinner>
)
const AuthLoading: React.FC = () => (
<Spinner>
<h3>AuthLoading</h3>
</Spinner>
)
const Spinner: React.FC = ({ children }) => (
<div
style={{
position: 'absolute',
display: 'flex',
top: 0,
bottom: 0,
left: 0,
right: 0,
backgroundColor: 'white',
}}
>
<div style={{ display: 'flex', flex: 1, justifyContent: 'center', alignItems: 'center' }}>
{children}
</div>
</div>
)
export const Main: React.FC = () => {
const [initiated, setInitiated] = useState(false)
useEffect(() => {
TokenUtil.setStorage(new AppStorage(setInitiated))
}, [])
useEffect(() => {
// eslint-disable-next-line no-console
console.log('is initiated: ', initiated)
}, [initiated])
const onAuthExpired = (errorMsg: RBAuthErrors, error?: Error) =>
setTimeout(() => {
alert(errorMsg)
// eslint-disable-next-line no-console
error && console.log(error)
})
if (!initiated) return <></>
return (
<Auth
authApi={AuthApi}
routes={{ private: '/super-secure', public: '/' }}
onAuthExpired={onAuthExpired}
appApis={GlobalAppApi}
>
<RefreshApp
locationPathName={'none'}
AuthReloadingComp={AuthReloading}
AuthLoadingComp={AuthLoading}
>
<App />
</RefreshApp>
</Auth>
)
}
Now, in your App
you can have the following snippet:
<BrowserRefresh AuthReloadingComp={Reloading}>
<Switch>
<SecureRoute
path='/secure'
Allowed={() => <h3>Secure area</h3>}
NotAllowed={() => <h3>You are not allowed</h3>}
/>
<SecureRoute path='/super-secure' Allowed={() => <h3>Super Secure area</h3>} />
</Switch>
<LoginLogout />
</BrowserRefresh>
Be sure you imported import { Switch } from 'react-router-dom';
.
BrowserRefresh
logic:
import React from 'react'
import { useLocation } from 'react-router-dom'
import { RefreshApp } from 'react-rb-auth'
export const BrowserRefresh: React.FC<{
AuthReloadingComp: React.FC
authCallbackRoute?: string
}> = ({ children, AuthReloadingComp, authCallbackRoute }) => {
const location = useLocation()
return (
<RefreshApp
locationPathName={location.pathname}
AuthReloadingComp={AuthReloadingComp}
authCallbackRoute={authCallbackRoute}
>
{children}
</RefreshApp>
)
}
Then in your index.tsx
or app.tsx
, whatever suits you best, under your redux provider, add the following to your react entry poing ( Auth
is our previously created app code):
ReactDOM.render(
<Provider store={store}>
<Auth>
<App />
</Auth>
</Provider>,
document.getElementById('root')
)
This library supplies you with a context to handle all Authentication state in the whole app for you. You will need to construct an AuthClass where you manage all your Authentication logic.
The context AuthContext
from import { AuthContext } from 'react-rb-auth';
has no idea about your specific UserModel
, so whenever you want to reach your user attributes throughout your codebase, provided Auth
has been introduced in the DOM tree (usually under your redux store provider), you will need to have the following type casted onto a "custom" AppAuthContext
of your own:
import { AuthContext, RBAuthReactContext } from 'react-rb-auth'
import { LoginType, SignupType, HandleType, SilentType, LogoutType, RefreshType } from './AuthApi'
import { GlobalAppApi } from './ExternalApi'
import { UserModel } from '../models/user'
import { rules } from '../models/rules'
type GlobalApi = typeof GlobalAppApi
export const AppAuthContext = AuthContext as RBAuthReactContext<
UserModel,
typeof rules,
LoginType,
LogoutType,
SignupType,
HandleType,
SilentType,
RefreshType,
GlobalApi
>
import { RBAuthUserModel } from 'react-rb-auth'
export interface UserModel extends RBAuthUserModel {
name: string
}
export const anonUser: UserModel = { name: '', role: 'visitor' }
export const regUser: UserModel = { name: 'Role Based Auth', role: 'admin' }
- You will have to define your own
UserModel
(it follows an example). In this case,UserModel
is simply an interface{ name: string, role: AppRole }
(notice the extraname
in the Object), beingRBAuthUserModel
imported fromreact-rb-auth
lib.
import { RBAuthUserModelWithRole } from 'react-rb-auth'
import { AppRole } from './role'
export interface UserModel extends RBAuthUserModelWithRole<AppRole> {
name: string
role: AppRole
}
export const anonUser: UserModel = {
name: '',
role: 'public',
}
AppRole
:
import { RBAuthBaseRoles } from 'react-rb-auth'
export type AppRole = 'writer' | RBAuthBaseRoles
- typeof rules, where
rules
is of typeRBAuthRulesInterface<AppRole>
LoginType
,LogoutType
,SignupType
,HandleType
,SilentType
,RefreshType
are all the necessary function types for the auth API (look atAuthApi.ts
file for an example as they are derived from the necessary Tokens and User type this library uses).GlobalApi
is a dictionary of multiple APIs. While it is still a WIP, it is being developed to allow us to catch any 'unauthorized' loggin in order for the AuthContext to automatically handle the error. You can override this by using your own apis and handling the exceptions yourself, while if the user is not authorized, you just need to log him out yourself.
Now you can use your context like follows, where we combined everything to let you see that even access to login
and logout
functionality is in the context. Notice the commented out Can
usage here.
import React from 'react'
// import { Can } from 'react-rb-auth';
import { Login } from './Login'
import { Logout } from './Profile'
import { AppAuthContext } from '../services/AppAuthContext'
export const LoginLogout: React.FC = () => (
// <Can role="admin" perform="dashboard-page:visit" yes={() => <Logout />} no={() => <Login />} />
<AppAuthContext.Consumer>
{(auth) => (
<>
{auth.isAuth && <Logout />}
{!auth.isAuth && <Login />}
</>
)}
</AppAuthContext.Consumer>
)
- TODO: further implement this. A basic implementation is underway and could be used with
Can
component.
In the project directory, you can run:
Builds the library code.
Builds the library code ( yarn prepare
), then builds the example app ( yarn predeploy
) and finally it deploys the app to GitHub pages ( yarn deploy
).
# (in another tab)
cd example
npm start # runs create-react-app dev server
Runs the test watcher in an interactive mode.
Publishes the library to NPM.
Just fork and do a PR :) I will add you to the colaborators list with a BIG thank you!
- Properly setup Roles
- ~~RequestBuilder timeout Fetch with timeout ~~
Whenever a new master
is deployed, it should be tagged with the new deployed version.
After we reach version 1.0.0 as the first release (production ready). After that, we follow semantic versioning.
Remember to always publish on a merge request. Pipeline master:only
actions will be created in the future, once we stabilize this library.
Enjoy!
- Add the library as a peer dependency in package.json (effectively requiring the calling project to provide this dependency)
- Add the library as a dev dependency in package.json (effectively allowing this library to successfully build without complaining about not having this dependency)
- Add the library to the externals config in your webpack.config file(s). By default, only react and react-dom are there, meaning that those are the only two libraries that you can use within your new shared library.
This project was bootstrapped with Create React Library.