When I first started this project, I had ambitions that I would be able to eventually turn this into something the larger audience could use. While I am proud at some of the work done here, I just have too much else to focus on. Feel free to use this library, fork it, build on it, etc. While I may occasionally contribute to this library, I am unlikely to maintain it on a regular cadence. I want to thank everyone that has used this library.
Redux Simple Auth is a library for implementing authentication and authorization within redux applications. Inspired by the wonderful Ember Simple Auth library, its aim is to make authentication / authorization simple and flexible for any application.
- Installation
- Development
- Usage
- How does it work?
- Configuration
- Authenticators
- Session Storage
- Authorizer
- Refreshing the session
- Actions
- Selectors
- Action Types
- TODO
- License
npm:
npm install --save redux-simple-auth
yarn:
yarn add redux-simple-auth
If you're trying to develop with this package check out the development docs for environment setup information.
This library ships with middleware, a reducer, and an optional store enhancer. You will need to do the following:
import { createStore, applyMiddleware } from 'redux'
import { createAuthMiddleware } from 'redux-simple-auth'
import rootReducer from './reducers'
// Authenticators and configuration options are discussed below
const authMiddleware = createAuthMiddleware({ authenticator: myAuthenticator })
const store = createStore(
rootReducer,
/* initialState, */
applyMiddleware(authMiddleware)
)
You will need to apply the reducer as session
. Redux Simple Auth does not
support custom names for the reducer key.
import { combineReducers } from 'redux'
import { reducer as session } from 'redux-simple-auth'
export default combineReducers({
// ...reducers
session
})
In order to use the enhancer, you will need to provide it with the storage used. If you do not need a custom storage adapter, you may import the default storage.
// ...
import {
createAuthMiddleware,
getInitialAuthState,
storage // or custom storage creator
} from 'redux-simple-auth'
import { createStore, compose, applyMiddleware } from 'redux'
const authMiddleware = createAuthMiddleware(/*...*/)
const store = createStore(
rootReducer,
/* initialState, */
compose(
applyMiddleware(authMiddleware),
getInitialAuthState({ storage })
)
)
Redux Simple Auth aims to make authentication and authorization within your application as flexible as possible. To get familiar with how to build authentication into your application, you will need to get familiar with a few terms.
Authenticator
An authenticator defines how your application authenticates a user and creates a session. An application may have one or many authenticators. The data returned from an authenticator will be saved using the specified session storage mechanism.
Session Storage
The session store persists the session state so that it may survive a page reload.
Authorizer
Authorizers are responsible for using the data stored in a session to generate authorization data that can be injected into outgoing requests.
To configure the middleware, simply pass the createAuthMiddleware
function the
configuration needed for your application. You may find more documentation on
each of these options below.
const authMiddleware = createAuthMiddleware({
authenticator: credentialsAuthenticator,
// or
authenticators: [facebookAuthenticator, githubAuthenticator],
authorize: jwtAuthorizer,
storage: localStorageStore,
refresh: refresher
})
Options:
-
authenticator
(object): An authenticator used to authenticate the session. This option is typically used if you only need a single method of authentication. This should not be used in conjunction withauthenticators
. -
authenticators
(array): An array of authenticators. If your application offers more than one method of authentication (Facebook Login, Github login, etc), you will pass the array of authenticators here. This option will be ignored if you use theauthenticator
option. -
storage
(object): The storage mechanism used to persist the session. -
authorize
(function): An authorization function used to attach header information to outgoing network requests. -
refresh
(function): A function used to refresh the session data after each request. -
syncTabs
(boolean): Determines whether session state should be synced across tabs or not.- Default:
false
- Default:
Authenticators implement the business logic responsible for authenticating the session. An application may have one or many authenticators such as authenticating credentials with one's own server, Facebook login, Github login, etc. The authentication strategy chosen is dependent on the action dispatched with the authentication payload.
store.dispatch(authenticate('credentials', { email, password }))
Redux Simple Auth ships with 2 authenticators. If you would like to build your own, refer to the custom authenticators documentation.
Credentials
An authenticator aimed to abstract away many of the common authentication scenarios used when authenticating via credentials. You may find more documentation on the options available below.
import { createCredentialsAuthenticator } from 'redux-simple-auth'
const credentialsAuthenticator = createCredentialsAuthenticator({
endpoint: '/api/authenticate'
})
When authenticating via the authenticate
action, simply give the credentials
payload as the second argument.
const credentials = { email: 'test@example.com', password: 'F@keP@ssword!' }
store.dispatch(authenticate('credentials', credentials))
Options
-
endpoint
(string): The endpoint that will be called with the credentials during authentication. -
contentType
(string): Specifies theContent-Type
header for the request.- Default:
application/json
- Default:
-
headers
(object): Allows you to define any additional headers for the request- Default:
{}
- Default:
-
method
(string): Allows you to define the HTTP method used for the request.- Default:
POST
- Default:
-
transformRequest
: (function): A function that accepts the credentials data and transforms it for the request body. This is useful if you need to encode the request body in a different way, such as anapplication/x-www-form-urlencoded
request.- Default:
JSON.stringify
- Default:
const credentialsAuthenticator = createCredentialsAuthenticator({
endpoint: '/api/authenticate',
contentType: 'application/x-www-form-urlencoded',
transformRequest(credentials) {
return Object.keys(credentials)
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(credentials[key])}`)
.join('&')
}
})
transformResponse
: (function): A function that allows you to transform the payload returned from the server that will be saved in thesession
state.- Default:
(payload) => payload
- Default:
const credentialsAuthenticator = createCredentialsAuthenticator({
endpoint: '/api/authenticate',
transformResponse: data => ({
token: data.response.token
})
})
-
restore
: (function): A restore function for the authenticator. The default implementation will resolve if the data is non-empty. If you would like more custom behavior, see the section on custom authenticators for usage information. -
invalidate
: (function): An invalidation function for the authenticator. The default implementation will always resolve. If you would like more custom behavior, see the section on custom authenticators for usage information.
OAuth2 Implicit Grant (alpha)
An authenticator to handle OAuth2 implicit grant flow. This validates that the
data passed to authenticate
has an access_token
parameter.
NOTE: This authenticator is currently in alpha. If you need more robust authentication/restore behavior, consider building your own custom authenticator.
import { createOauth2ImplicitGrantAuthenticator } from 'redux-simple-auth'
const oauth2ImplicitGrantAuthenticator = createOauth2ImplicitGrantAuthenticator()
Options
There are currently no options for this authenticator. As OAuth2 support is built out, options will be added to better support extensibility.
To implement your own custom authenticator, you will need to import the
createAuthenticator
function from Redux Simple Auth. To create the
authenticator, simply call the function with a configuration object.
import { createAuthenticator } from 'redux-simple-auth'
const credentialsAuthenticator = createAuthenticator({
name: 'credentials',
authenticate(data) {
// ...
},
invalidate(data) {
// ...
},
restore(data) {
// ...
}
})
Options:
-
name
(string): The name of the authenticator. This is used by the middleware to identify the authenticator used during the lifecycle of the session. -
authenticate(data)
(function): A function responsible for implementing the logic responsible for authentication. This function will be invoked when theauthenticate
action is dispatched. It accepts a single argument of data given to theauthenticate
action and must return a promise. A resolved promise will indicate that the session is successfully authenticated. Any data resolved with the promise will be stored and accessible via thesession
state. A rejected promise will indicate authentication failed and will result in an unauthenticated session. Note that a default implementation of this function is defined if none is given and always returns a rejected promise resulting in an unauthenticated session. It is important that this function is defined when creating your authenticator. -
invalidate(data)
(function): A function responsible for doing any additional cleanup of the authenticated data. This function will be invoked when theinvalidateSession
action is dispatched. It accepts a single argument with the data persisted to the session and must return a promise. A resolved promise will clear the authenticated session data and result in an unauthenticated session. A rejected promise will leave the session authenticated. Note that a default implementation of this function is defined if none is given and always returns a resolved promise. -
restore(data)
(function): A function used to restore the session, typically after a page refresh. This function will be invoked when the middleware is first created. It accepts an argument with the data persisted to the session and must return a promise. A resolved promise indicates the session restore was successful and will result in the session successfully authenticated. A rejected promise indicates the session restore was unsuccessful and will result in an unauthenticated session. Note that a default implementation of this function is defined if none is given and always returns a rejected promise resulting in an unauthenticated session. It is important that you define this function when creating your authenticator.
Example:
Let's create a basic credentials authenticator that accepts an email and password. The authenticator will use the credentials, authenticate with the server, and return a token given by the server upon successful authentication.
configureStore.js
import { createAuthMiddleware, createAuthenticator } from 'redux-simple-auth'
import { createStore, applyMiddleware } from 'redux'
const credentialsAuthenticator = createAuthenticator({
name: 'credentials',
authenticate(credentials) {
return fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(credentials)
}).then(({ token }) => ({ token }))
},
restore(data) {
if (data.token) {
return Promise.resolve(data)
}
return Promise.reject()
},
invalidate(data) {
return fetch('/api/invalidate', { method: 'DELETE' })
}
})
const authMiddleware = createAuthMiddleware({
authenticator: credentialsAuthenticator
})
// if combined with other authenticators
const authMiddleware = createAuthMiddleware({
authenticators: [...authenticators, credentialsAuthenticator]
})
Session storage is responsible for persisting the session state so that it may survive a page refresh. Only one session store can be defined per application. Redux Simple Auth makes it easy to swap the session storage to meet your needs.
import {
createAuthMiddleware,
createLocalStorageStore
} from 'redux-simple-auth'
const localStorageStore = createLocalStorageStore()
const authMiddleware = createAuthMiddleware({
storage: localStorageStore
})
Redux Simple Auth ships with 4 session stores.
localStorage
Store
The local storage store stores its data in the browser's localStorage
.
sessionStorage
Store
The session storage store stores its data in the browser's sessionStorage
.
Cookie Store
The cookie store stores its data in a cookie.
Adaptive Store
If localStorage
is available the adaptive store will use the local storage
store. If not, it will fallback to using the cookie store. This is the default
store.
It is easy to customize a store. To do so, just import its respective create*
function to define it.
localStorage
store
import {
createAuthMiddleware,
createLocalStorageStore
} from 'redux-simple-auth'
const localStorageStore = createLocalStorageStore({
key: 'my-custom-app-key'
})
const authMiddleware = createAuthMiddleware({
storage: localStorageStore
})
Options:
key
(string): ThelocalStorage
key used to persist the session.- Default:
'redux-simple-auth-session'
- Default:
sessionStorage
store
import {
createAuthMiddleware,
createSessionStorageStore
} from 'redux-simple-auth'
const sessionStorageStore = createSessionStorageStore({
key: 'my-custom-app-key'
})
const authMiddleware = createAuthMiddleware({
storage: sessionStorageStore
})
Options:
key
(string): ThesessionStorage
key used to persist the session.- Default:
'redux-simple-auth-session'
- Default:
Cookie store
import {
createAuthMiddleware,
createCookieStore
} from 'redux-simple-auth'
const cookieStore = createCookieStore({
name: 'my-custom-app-cookie',
path: '/',
domain: 'example.com',
secure: true,
expires: 120
})
const authMiddleware = createAuthMiddleware({
storage: cookieStore
})
Options:
-
name
(string): The name of the cookie used to persist the session- Default:
'redux-simple-auth-session'
- Default:
-
path
(string): A custom path used for the cookie (e.g.'/something'
)- Default:
'/'
- Default:
-
domain
(string): The domain to use for the cookie (e.g.'example.com'
,.example.com
to include all subdomains, or'subdomain.example.com'
). If not explicitly set, the cookie domain will default to the domain the session was authenticated on.- Default:
null
- Default:
-
secure
(boolean): Determines how the cookie should set the secure flag.- Default:
false
- Default:
-
expires
(number): The expiration time for the cookie in seconds. A value ofnull
will make the cookie expire and get deleted when the browser is closed.- Default:
null
- Default:
Adaptive Store
import {
createAuthMiddleware,
createAdaptiveStore
} from 'redux-simple-auth'
const adaptiveStore = createAdaptiveStore({
localStorageKey: 'my-custom-app-key',
cookieName: 'my-custom-app-name',
cookiePath: '/',
cookieDomain: 'example.com',
cookieSecure: true,
cookieExpires: 120
})
const authMiddleware = createAuthMiddleware({
storage: adaptiveStore
})
Options:
See the options for each store to for usage and defaults. If local storage is available, the local storage store will get created using the local storage options. If not, the cookie options will be passed to the cookie store upon creation.
To implement your own session store, simply define an object that handles the serialization and deserialization of data.
const mySessionStorage = {
persist(data) {
saveMyData(JSON.stringify(data))
},
restore() {
return JSON.parse(getMyDataBack()) || {}
}
}
const authMiddleware = createAuthMiddleware({
storage: mySessionStorage
})
Options:
-
persist
(function): A serialization function that persists the session data. -
restore
(function): A deserialization function that restores session data.
An authorizer is responsible for setting up any needed data for outgoing network
requests. This function is invoked by the middleware when a
fetch
action is dispatched.
Redux Simple Auth currently ships with 1 authorizer. As this library matures, there are plans to implement more built-in authorizers. Refer to the custom authorizers section to build your own.
OAuth2 Bearer
This authorizer is responsible for setting the Authorization
header using the
Bearer
scheme.
import { createAuthMiddleware, oauth2BearerAuthorizer } from 'redux-simple-auth'
const authMiddleware = createAuthMiddleware({
authorize: oauth2BearerAuthorizer
})
To implement a custom authorizer, simply define a function that accepts two arguments: the session data, and a callback function.
const bearerAuthorizer = (data, block) => {
if (data.token) {
block('Authorization', `Bearer ${data.token}`)
}
}
const authMiddleware = createAuthMiddleware({
authorize: bearerAuthorizer
})
Arguments:
-
data
(object): The session data -
block
(function): A callback function responsible for defining any headers needed for authorization. It accepts a header name for its first argument and that header's value as its second argument.
There may be cases where you may want the redux store initialized with the sesion data from the storage device. The store enhancer does just that. On store initialization, it will ask the storage device for the session data.
const enhancer = getInitialAuthState({ storage })
Options:
storage
(object): The storage mechanism used to store the session. This must be the same storage device configured with the middleware.
There may be cases where you need to refresh the session data after each request. For example, you may implement sliding sessions where requests to your backend give you an updated session token.
To use this feature, simply define a refresh
function that accepts the raw
response as an argument. Note, this will only get called for requests made
through the dispatched fetch
action.
const refresh = response => ({
token: response.headers.get('x-access-token')
})
const authMiddleware = createAuthMiddleware({
refresh
})
There may be cases where you want to conditionally update the session. To skip
the session update, simply return null
from your refresh
function.
const refresh = response => {
const contentType = response.headers.get('content-type')
if (contentType === 'text/html') {
return null
}
return { token: response.headers.get('x-access-token') }
}
Arguments:
response
(Response): The raw response returned fromfetch
Redux Simple Auth ships with several actions to aide in authentication for your app. Simply import them and dispatch them as necessary.
To authenticate the session, use the authenticate action. The middleware will
look up the corresponding authenticator and invoke its authenticate
function.
import { authenticate } from 'redux-simple-auth'
store.dispatch(
authenticate('credentials', {
email: 'user@example.com',
password: 'password'
})
)
Arguments:
-
authenticator
(string): The name of the authenticator used for authentication. The middleware will invoke this authenticator'sauthenticate
function. -
payload
(any): The payload given to the authenticator when authenticating. The middleware will pass this payload as an argument directly to theauthenticate
function.
Fetch an endpoint that requires authentication. If an authorizer is configured with the the middleware, the middleware will invoke the authorizer to attach any headers needed for authentication.
It is important that you use this action and forego using window.fetch
when
interacting with a server that requires authentication using data defined in the
session. This will invoke window.fetch
under the hood after attaching any
authorization specific information from the authorizer. The API is the same API
used for window.fetch
so you may use it interchangeably.
import { fetch } from 'redux-simple-auth'
store.dispatch(
fetch('https://www.example.com/me', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: 'Bob' })
})
).then(res => res.json())
// etc
Clears the last error and resets failed auth state. Useful if a user has failed authentication but has navigated away from the form.
import { clearError } from 'redux-simple-auth'
store.dispatch(clearError())
Invalidate the session. This will clear the authenticated session data and result in an unauthenticated session.
import { invalidateSession } from 'redux-simple-auth'
store.dispatch(invalidateSession())
Update the session with new data. If you are using the refresh
option for the
middleware, this will automatically be dispatched for you. Use this only if you
need to manually update the session data outside of the request lifecycle.
import { updateSession } from 'redux-simple-auth'
store.dispatch(updateSession({ token: 'a-new-token' }))
To aid in selecting specific session state, redux simple auth ships with a few
selectors for your convenience. All selectors take the store state
as an
argument. Note this is the entire store state, not just the session state.
(object) Returns the session data set when user was authenticated. If not yet authenticated, this returns an empty object.
import { getSessionData } from 'redux-simple-auth'
const mapStateToProps = state => ({
session: getSessionData(state)
})
(boolean) Returns whether the user is authenticated.
import { getIsAuthenticated } from 'redux-simple-auth'
const mapStateToProps = state => ({
isAuthenticated: getIsAuthenticated(state)
})
(string) Returns the authenticator
used when authenticating. If not yet
authenticated, this is set to null
.
import { getAuthenticator } from 'redux-simple-auth'
const mapStateToProps = state => ({
authenticator: getAuthenticator(state)
})
(boolean) Returns whether the session state has been restored. Useful if you need to block rendering until the session state has been fully initialized.
import { getIsRestored } from 'redux-simple-auth'
const mapStateToProps = state => ({
isRestored: getIsRestored(state)
})
(any) Returns the last authentication error received if authentication has
failed. This value is the same value passed to the rejected promise in the
authenticator's authenticate
function.
import { getLastError } from 'redux-simple-auth'
const mapStateToProps = state => ({
lastError: getLastError(state)
})
(boolean) Returns whether the user has at least one failed authentication
attempt. Will reset back to false
once authentication has succeeded.
import { getHasFailedAuth } from 'redux-simple-auth'
const mapStateToProps = state => ({
hasFailedAuth: getHasFailedAuth(state)
})
If you just plain need to hook into actions dispatched from redux-simple-auth
,
you may import the action types themselves for use within your own reducers.
import { actionTypes } from 'redux-simple-auth'
const reducer = (state, action) => {
switch (action.type) {
case actionTypes.AUTHENTICATE_FAILED:
// do something
}
}
The following actions are available action types
AUTHENTICATE
AUTHENTICATE_FAILED
AUTHENTICATE_SUCCEEDED
CLEAR_ERROR
FETCH
INVALIDATE_SESSION
INVALIDATE_SESSION_FAILED
RESTORE
RESTORE_FAILED
UPDATE_SESSION
- Built-in authenticators for common scenarios
- Credentials
- Devise
- OAuth
- Facebook Login
- Github Login
- Google login
- Built-in authorizers
- Devise
- OAuth2 Bearer
- Integration with React Router v3
- Integration with React Router v4
- Typescript/Flow support
- Create example applications
- Solutions for server-side rendering
- Sync state across tabs
MIT