react-check-auth
v0.2.0-alpha.2
Published
Declaratively add auth protection anywhere in your react/react-native app
Downloads
216
Readme
react-check-auth
react-check-auth
is a tiny react component that helps you make auth checks declarative in your react or react-native app.
This component uses React 16's new context API and is just ~100 LOC. It can also serve as a boilerplate for getting familiar with using the context API to pass information from a parent component to arbitrarily deep child components.
Motivation
In a typical app UI, depending on whether the user logs in, components in the application display different information.
For example, a "welcome user" label or a "login button" on a header. Or using this information with routing, /home
should redirect to /login
if the user is not logged in, and /login
should redirect to /home
if the user is logged in.
Before react-check-auth
- On load, your app must make a request to some kind of a
/verifyUser
or a/fetchUser
endpoint to check if the existing persisted token/cookie is available and valid. - You need to store that information in app state and pass it as a prop all through your component tree just so that that child components can access it or use
redux
to store the state andconnect()
the consuming component.
After react-check-auth
- You specify the
authUrl
endpoint as a prop to a wrapper component called<AuthProvider
. - You access logged-in information by wrapping your react component/element in
<AuthConsumer>
that has the latest props.
You don't need to make an API request, or pass props around, or manage state/reducers/connections in your app.
Example
1) Add AuthProvider
Wrap your react app in a AuthProvider
component that has an endpoint to fetch basic user information. This works because if the user had logged in, a cookie would already be present. For using authorization headers, check the docs after the examples.
import React from "react";
import ReactDOM from "react-dom";
import {AuthProvider} from "react-check-auth";
import {Header, Main} from "./components";
const App = () => (
<AuthProvider authUrl={'https://website.com/get/userInfo'}>
<div>
// The rest of your react app goes here
<Header />
<Main />
</div>
</AuthProvider>
);
ReactDOM.render(<App />, document.getElementById("root"));
2) Show a "welcome user" or a Login button
Now, in any arbitrary component, like a Header, you can check if the user is currently logged in. Typically you would use this for either showing a "welcome" label or a login button.
import {AuthConsumer} from 'react-check-auth';
const Header = () => (
<div>
// Use the AuthConsumer component to check
// if userInfo is available
<AuthConsumer>
{({userInfo, isLoading, error}) => (
userInfo ?
(<span>Hi {userInfo.username}</span>) :
(<a href="/login">Login</a>)
)}
</AuthConsumer>
</div>
);
3) Redirect not-logged in users to /login
You can mix and match react-check-auth
with other declarative components like routing:
import {AuthConsumer} from 'react-check-auth';
const Main = () => (
<Router>
<Route path='/home' component={Home} />
<Route path ='/login' component={Login} />
</Router>
);
const Home = () => {
return (
<AuthConsumer>
{({userInfo}) => {
// Redirect the user to login if they are not logged in
if (!userInfo) {
return (<Redirect to='/login' />);
}
// Otherwise render the normal component
else {
return (<div>Welcome Home!</div>);
}
}}
</AuthConsumer>
);
}
);
Usage guide
I. Backend requirements
These are the backend requirements that are assumed by react-check-auth
.
1) API endpoint to return user information
An API request to fetch user information. It should take a cookie, or a header or a body for current session information.
For example:
GET https://my-backend.com/api/user
Content-Type: application/json
Cookie: <...>
Authorization: Bearer <...>
2) Success or logged-in response
If the user is logged in, the API should return a 200
status code with a JSON
object.
For example:
{
"username": "iamuser",
"id": 123
}
3) Not logged-in response
If the user is not logged-in, the API should return a non 200
status code:
For example:
Status: 403
II. Installation
$ npm install --save react-check-auth
III. Set up AuthProvider
The AuthProvider
component should be at the top of the component tree so that any other component in the app can consume the userInfo
information.
The AuthProvider
takes a required prop called authUrl
and an optional prop called reqOptions
.
<AuthProvider authUrl="https://my-backend.com/api/user" reqOptions={requestOptionsObject} />
authUrl
:: String
Should be a valid HTTP endpoint. Can be an HTTP endpoint of any method.
reqOptions
:: Object || Function
Should be or return a valid fetch
options object as per https://github.github.io/fetch/#options.
Note: This is an optional prop that does not need to be specified if your authUrl
endpoint is a GET endpoint that accepts cookies.
Default value that ensures cookies get sent to a GET
endpoint:
{
"method": "GET",
"credentials": "include",
"headers": {
"Content-Type": "application/json"
},
}
Example 1: Use a GET endpoint with cookies
import React from 'react';
import {AuthProvider} from 'react-check-auth';
const authUrl = "https://my-backend.com/verifyAuth";
const App = () => (
<AuthProvider authUrl={authUrl}>
// The rest of your app goes here
</AuthProvider>
);
Example 2: Use a GET endpoint with a header
import React from 'react';
import {AuthProvider} from 'react-check-auth';
const authUrl = "https://my-backend.com/verifyAuth";
const reqOptions = {
'method': 'GET',
'headers': {
'Content-Type': 'application/json',
'Authorization' : 'Bearer ' + window.localStorage.myAuthToken
},
};
const App = () => (
<AuthProvider authUrl={authUrl} reqOptions={reqOptions}>
// The rest of your app goes here
</AuthProvider>
);
Example 3: Use a POST endpoint with updated token
import React from 'react';
import {AuthProvider} from 'react-check-auth';
const authUrl = "https://my-backend.com/verifyAuth";
const reqOptions = () => {
'method': 'POST',
'headers': {
'Content-Type': 'application/json',
'Authorization' : 'Bearer ' + window.localStorage.myAuthToken
},
};
const App = () => (
<AuthProvider authUrl={authUrl} reqOptions={reqOptions}>
// The rest of your app goes here
</AuthProvider>
);
IV. Consuming auth state with <AuthConsumer>
Any react component or element can be wrapped with an <AuthConsumer>
to consume the latest contextValue. You must write your react code inside a function that accepts the latest contextValue. Whenver the contextValue is updated then the AuthComponent is automatically re-rendered.
For example,
<AuthConsumer>
{(props) => {
props.userInfo = {..} // <request-object> returned by the API
props.isLoading = true/false // if the API has not returned yet
props.error = {..} // <error-object> if the API returned a non-200 or the API call failed
}}
</AuthConsumer>
props.userInfo
:: JSON
If the API call returned a 200 meaning that the current session is valid, userInfo
contains as returned by the API.
If the API call returned a non-200 meaning that the current session is absent or invalid, userInfo
is set to null
.
props.isLoading
:: Boolean
If the API call has not returned yet, isLoading: true
. If the API call has not been made yet, or has completed then isLoading: false
.
props.error
:: JSON
If the API call returned a non-200 or there was an error in making the API call itself, error
contains the parsed JSON value.
V. Refresh state (eg: logout)
If you implement a logout action in your app, the auth state needs to be updated. All you need to do is call the refreshAuth()
function available as an argument in the renderProp function of the AuthConsumer
component.
For example:
<AuthConsumer>
{(refreshAuth) => (
<button onClick={{
this.logout() // This is a promise that calls a logout API
.then(
() => refreshAuth()
);
}}>
Logout
</button>
</AuthConsumer>
This will re-run the call to authUrl
and update all the child components accordingly.
VI. Using with React Native
Usage with React Native is exactly the same as with React. However you would typically use a Authorization header instead of cookies. Here's a quick example:
import { AuthProvider, AuthConsumer } from 'react-vksci123';
export default class App extends Component<Props> {
render() {
const sessionToken = AsyncStorage.getItem("@mytokenkey");
const reqOptions = {
"method": "GET",
"headers": sessionToken ? { "Authorization" : `Bearer ${sessionToken}` } : {}
}
return (
<AuthProvider
authUrl={`https://my-backend.com/api/user`}
reqOptions={reqOptions}
>
<View style={styles.container}>
<Text style={styles.welcome}>
Welcome to React Native!
</Text>
<AuthConsumer>
{({isLoading, userInfo, error}) => {
if (isLoading) {
return (<ActivityIndicator />);
}
if (error) {
return (<Text> Unexpected </Text>);
}
if (!userInfo) {
return (<LoginComponent />);
}
return (<HomeComponent />);
}}
</AuthConsumer>
</View>
</AuthProvider>
);
}
}
Plug-n-play with existing auth providers
All Auth backend providers provide an endpoint to verify a "session" and fetch user information. This component was motivated from creating documentation for integrating Hasura's auth backend into a react app with minimum boilerplate. That said this package is meant to be used with any auth provider, including your own.
Hasura
Hasura's Auth API can be integrated with this module with a simple auth get endpoint and can also be used to redirect the user to Hasura's Auth UI Kit in case the user is not logged in.
// replace CLUSTER_NAME with your Hasura cluster name.
const authEndpoint = 'https://auth.CLUSTER_NAME.hasura-app.io/v1/user/info';
// pass the above reqObject to CheckAuth
<AuthProvider authUrl={authEndpoint}>
<AuthConsumer>
{ ({ isLoading, userInfo, error }) => {
// your implementation here
} }
</AuthConsumer>
</AuthProvider>
Read the docs here.
Firebase
CheckAuth
can be integrated with Firebase APIs.
// replace API_KEY with your Firebase API Key and ID_TOKEN appropriately.
const authUrl = 'https://www.googleapis.com/identitytoolkit/v3/relyingparty/getAccountInfo?key=[API_KEY]';
const reqObject = { 'method': 'POST', 'payload': {'idToken': '[ID_TOKEN]'}, 'headers': {'content-type': 'application/json'}};
// pass the above reqObject to CheckAuth
<AuthProvider authUrl={authUrl} reqObject={reqObject}>
<AuthConsumer>
{ ({ isLoading, userInfo, error }) => {
// your implementation here
} }
</AuthConsumer>
</AuthProvider>
Custom Provider
CheckAuth
can be integrated with any custom authentication provider APIs.
Lets assume we have an endpoint on the backend /api/check_token
which reads a header x-access-token
from the request and provides with the associated user information
const authEndpoint = 'http://localhost:8080/api/check_token';
const reqOptions = {
'method': 'GET',
'headers': {
'Content-Type': 'application/json',
'x-access-token': 'jwt_token'
}
};
<AuthProvider authUrl = { authEndpoint } reqOptions={ reqOptions }>
<AuthConsumer>
{ ( { isLoading, userInfo, error, refreshAuth }) => {
if ( !userInfo ) {
return (
<span>Please login</span>
);
}
return (
<span>Hello { userInfo ? userInfo.username.name : '' }</span>
);
}}
</AuthConsumer>
</AuthProvider>
It will render as <span>Please login</span>
if the user's token is invalid and if the token is a valid one it will render Hello username
How it works
- The
AuthProvider
component uses theauthUrl
andreqOptions
information given to it to make an API call - While the API call is being made, it sets the context value to have
isLoading
totrue
.
{
"userInfo": null,
"isLoading": true,
"error": null
}
- Once the API call returns, in the context value
isLoading
is set to `false' and: - Once the API call returns, if the user is logged in, the AuthProvider sets the context to
userInfo: <response-object>
{
"userInfo": <response-object>,
"isLoading": false,
"error": null
}
- If the user is not logged in, in the context value,
userInfo
is set tonull
anderror
is set to the error response sent by the API, if the error is in JSON.
{
"userInfo": null,
"isLoading": false,
"error": <error-response>
}
- If the API call fails for some other reason,
error
contains the information
{
"userInfo": null,
"isLoading": false,
"error": <error-response>
}
- Whenever the contextValue is updated, any component that is wrapped with
AuthConsumer
will be re-rendered with the contextValue passed to it as an argument in the renderProp function:
<AuthConsumer>
{ ({userInfo, isLoading, error}) => {
return (...);
}}
<AuthConsumer>
Contributing
Clone repo
git clone https://github.com/hasura/react-check-auth.git
Install dependencies
npm install
or yarn install
Start development server
npm start
or yarn start
Runs the demo app in development mode.
Open http://localhost:3000 to view it in the browser.
Source code
The source code for the react components are located inside src/lib
.
Demo app
A demo-app is located inside src/demo
directory, which you can use to test your library while developing.
Testing
npm run test
or yarn run test
Build library
npm run build
or yarn run build
Produces production version of library under the build
folder.
Maintainers
This project has come out of the work at hasura.io. Current maintainers @Praveen, @Karthik, @Rishi.