Code Monkey home page Code Monkey logo

react-check-auth's Introduction

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

  1. 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.
  2. 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 and connect() the consuming component.

After react-check-auth

  1. You specify the authUrl endpoint as a prop to a wrapper component called <AuthProvider.
  2. 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

How it works

  1. The AuthProvider component uses the authUrl and reqOptions information given to it to make an API call
  2. While the API call is being made, it sets the context value to have isLoading to true.
{
  "userInfo": null,
  "isLoading": true,
  "error": null
}
  1. Once the API call returns, in the context value isLoading is set to `false' and:
  2. 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
}
  1. If the user is not logged in, in the context value, userInfo is set to null and error is set to the error response sent by the API, if the error is in JSON.
{
  "userInfo": null,
  "isLoading": false,
  "error": <error-response>
}
  1. If the API call fails for some other reason, error contains the information
{
  "userInfo": null,
  "isLoading": false,
  "error": <error-response>
}
  1. 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.

react-check-auth's People

Contributors

coco98 avatar cvinson avatar karthikvt26 avatar musab avatar praveenweb avatar viktor-ku avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-check-auth's Issues

TypeError: o.default.createContext is not a function

I have create-react-app with react-scripts 1.1.4. Now when I am importing this module like

import { AuthProvider } from 'react-check-auth'

It gives me this

TypeError: o.default.createContext is not a function

(anonymous function)
node_modules/react-check-auth/build/index.js:1

reqOptions as a function

Hi

Problem

There is a certain case when you need to to check auth with updated headers, cause it makes sense, right?

In my project right now I am hacking this stuff by creating an options object somewhere and accessing it like that

import checkAuthOptions from '../lib/checkAuthOptions'

Then I'd modify it and because it's reference check auth will use updated object

Another way to do it is via observables but I don't want to bring them just to use in this case.

Proposal

So my proposal is to make reqOptions either a function or object. So I'd write

const reqOptions = () => ({
  method: 'POST',
  headers: {
    Authorization: localStorage.getItem('awesomeness-token')
  }
})

And before doing any fetch check auth would execute this function to get current tokens and everything.

Backward Compatibility

To maintain backward compatibility we can pass reqOptions as an object or a function

How can I grab the userInfo in the requestionOptions function

I'm getting TypeError: Cannot read property 'userInfo' of null

Just getting started with react so I think this is for the App class :/ #help

class App extends Component {
  requestOptionsObject() {
    const { accessToken, client, uid } = this.state.userInfo;
    return {
      headers: { "Access-Control-Allow-Origin": "*" },
      "access-token": accessToken,
      client: client,
      uid: uid
    };
  }
  render() {
    return (
      <AuthProvider
        authUrl={"http://localhost:3000/auth/validate_token"}
        reqOptions={this.requestOptionsObject()}
      >
        <Home />
      </AuthProvider>
    );
  }
}

[Question] Isn't the AuthConsumer redundant?

This is a nice library and a very good example on using React v16 Context API.
Thank you for creating it.

The technique used in AuthProvider.js is very interesting. I hope to use it in my project.

It seemed to me AuthConsumer.js could be simplified to just two lines as below.
Would that be correct?

import { Consumer } from '../context';
export default Consumer;

v1?

Hi. Great work!

What is missing for it to be v1?

How can I help?

Pass reqOptions to refreshAuth

It can be a nice feature to make this lib even more robust.

This problems comes from #9

It's just another way to solve it

If you don't pass it will use default object (*or function)

Any thoughts?

Add TypeScript support

It can be either

  1. Add .d.ts file
  2. Add ts-scripts and use ts definitions in the components

Any thoughts?

Expose refreshAuth

Hi,

Can we expose refreshAuth somehow?

Sometimes you just need some sort of signOut function that will handle

  • removing token from localstorage
  • ...
  • and lastly we need to update check auth cycle to show sign In form again

Can/Will you do it? Or can you just give me a hint? Or you think it's a bad idea?

no request when no token

Hi is there a way, to not to do a request when there is no jwt token in localStorage?
It should just render the login page

Now it is doing an unneccessary empty request, and the response is also empty. so the userInfo is null.
We should be able to skip that.., do you know how?

TypeError: __WEBPACK_IMPORTED_MODULE_0_react___default.a.createContext is not a function

I want to used context with two component, created context in parent as ๐Ÿ‘
export const settingContext = React.createContext(null);
created context and use that context with settingValue which is true, ProviderInfo is component
<settingContext.Provider value={settingValue}>

</settingContext.Provider>
I have setstate the settingValue to true.
And in ProviderInfo component use it like :
<settingContext.Consumer>
{this.props.settingValue? :''}
</settingContext.Consumer>
But I'm getting this kind of error.

Functions are not valid as a React child

HI, thanks for this cool project.
Started a new create-react-app project and followed your instructions,

<AuthProvider authUrl={authConfig.authUrl} reqOptions={authConfig.reqOptions}>
    <div>
      {({userInfo, isLoading, error}) => (
        userInfo ?
          (<span>Hi {userInfo.username}</span>) :
          (<a href="/login">Login</a>)
      )}
    </div>
  </AuthProvider>

And it looks like context API isn't working for me (or kind of), as I get following warning and me not logged in message dos not show up.

index.js:1452 Warning: Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it.

My API returns a 401, like this

{message: "Invalid token."}

Packages versions:
"@material-ui/core": "^3.2.0",
"@material-ui/icons": "^3.0.1",
"connected-react-router": "4.5.0",
"material-ui-icons": "^1.0.0-beta.36",
"node-sass": "^4.9.3",
"promise-polyfill": "^8.1.0",
"react": "16.5.2",
"react-check-auth": "^0.2.0-alpha.2",
"react-dom": "16.5.2",
"react-redux": "5.0.7",
"react-router": "4.3.1",
"react-router-dom": "4.3.1",
"redux": "4.0.0",
"redux-thunk": "2.3.0",
"sanitize.css": "7.0.3",
"serve": "10.0.2",
"whatwg-fetch": "^3.0.0"

Any suggestions help, very much appreciated

Redirect not work on browser refresh

Very useful component, thank you.

In the Redirection example on the Readme Doc,
https://github.com/hasura/react-check-auth#3-redirect-not-logged-in-users-to-login

If I just use Browser history router, it works well.
But if I refresh the browser the example code not work, because #setState() methods called 3 times.
First on initial rendering, second on toggleLoading(), and finally on fetchSuccess()/ Fail().
The UserInfo is null when initial rendering, so the example code trigger redirection to login url.

I think some other property is required that distinguish between initial rendering and the last rendering to solve this problem.

Thanks.

Oauth2 Password flow

It's not clear where to store the refresh token once the user logs in using a login form. Once the user enters their credentials and receives a token, where then should the token be saved?

Babel7 Update

it's time to not have 30 dependencies installed that are a year old and all pretty much deprecated compared to @babel/core and @babel/helper, etc.... .

es2015 is 4 years old. we're all the way past 2016,2017, 2018 and we just use env now.

would be lovely to use this, but it's going in the bin because it requires WAY too many dependencies that i don't need for anything else because everything i have is modern and up to date.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.