React Auth

Easy authentication implementation with React SPA

Getting started

The React Auth package is a simple package to help you implement authentication in a React Single Page Application. It uses the context API behind the scenes to make authentication available.

First, in your client-side application, run the following command:

yarn add @tensei/react-auth

Next, you need to generate the types for your own custom Tensei API. In your client application, run the command yarn tensei-sdk generate (or, for short yarn tensei-sdk g). This would read your Tensei API, and generate types for the sdk. You would have to run this command everytime you make changes to your API to get the latest changes.

yarn tensei-sdk generate 

# or 

yarn tensei-sdk g

By default, the sdk would fetch the types from http://localhost:8810, but if your API is running on a different url, you may provide the url as the second parameter:

yarn tensei-sdk g http://localhost:5006

Setting up the auth provider

Next, import the provider and wrap your whole application with it:

import ReactDOM from 'react-dom'
import { TenseiAuthProvider } from '@tensei/react-auth'

import App from './App'

ReactDOM.render(
    <TenseiAuthProvider>
        <App />
    </TenseiAuthProvider>
)

The provider will take care of checking if the user is already authenticated, handling social authentication callback, setting and unsetting the authenticated user, silently refreshing the access token, and so much more.

Examples

Sample authentication

The following is an example on how to setup authentication.

  • The useAuth hook gives you access to the authenticated user and access token.
  • The useTensei hook gives you the sdk instance you can use to interact with your Tensei api.
import ReactDOM from 'react-dom'
import { TenseiAuthProvider, useAuth, useTensei } from '@tensei/react-auth'

const App = () => {
    // Get the sdk instance
    const tensei = useTensei()
    // Get the user. If the user is currently being fetched, isLoading will be true.
    const { isLoading, user } = useAuth()

    // A function to login a user. This will handle setting the user and access token.
    // You may need to redirect the user after successful authentication.
    const login = () => {
        tensei.auth().login({
            object: {
                email: 'hey@tenseijs.com',
                password: 'password'
            }
        })
    }

    // A function to logout the currently authenticated user. user from useAuth() will become undefined. 
    const logout = () => tensei.auth().logout()

    // Handle the loading state in your component. You may want to display a spinner here.
    if (isLoading) {
        return <h2>Loading authenticated user...</h2>
    }

    // Check if the user is authenticated by checking `user` variable. 
    if (user) {
        return (
            <>
                Hello, <b>{user.email}</b>
                <button onClick={logout}>Logout</button>
            </>
        )
    }

    // Render a login button or page if user is not authenticated.
    return (
        <button onClick={login}>Login</button>
    )
}

ReactDOM.render(
    <TenseiAuthProvider>
        <App />
    </TenseiAuthProvider>,
    document.querySelector('#app')
)

With React Router

Here's an example using React Router:

import ReactDOM from 'react-dom'
import { Router, Route, Link } from 'react-router-dom'
import { TenseiAuthProvider, useAuth, useTensei, MustBeAuthenticated, MustBeNotAuthenticated } from '@tensei/react-auth'

const LoginPage = () => {
    const tensei = useTensei()

    const onSubmit = (event: any) => {
        event.preventDefault()

        const [email, password] = event.target.elements

        tensei.auth().login({
            object: {
                email: email.value,
                password: password.value
            }
        })
    }

    return (
        <div>
            <form onSubmit={onSubmit}>
                <input type="text" name="email" placeholder="email" />
                <input type="password" name="password" placeholder="password" />

                <br />
                <br />
                <button>login to your account</button>
            </form> 
        </div>
    )
}   

const DashboardPage = () => {}

//
const WelcomePage = () => {
    return (
        <div>
            <h1>Welcome to our beautiful app</h1>
            <br />
            <Link to='/login'>Login to your account</Link>
        </div>
    )
}

ReactDOM.render(
    <TenseiAuthProvider>
        <Router>
            <Route path='/' exact component={WelcomePage} />
            {/* This route can only be accessed by users who are not authenticated */}
            <Route path='/login' exact component={MustBeNotAuthenticated(LoginPage)} />
            {/* This route can only be accessed by authenticated users */}
            <Route path='/dashboard' exact component={MustBeAuthenticated(DashboardPage)} />
        </Router>
    </TenseiAuthProvider>,
    document.querySelector('#app')
)

With custom redirect callback

By default, if a user is not authenticated, they're redirected using window.location.href = '/login'. This may not provide the best user experience since it'll cause a full page refresh. If you're using React Router or another routing library, you may want to redirect using client-side routing. To do this, you may pass the onRedirectCallback to the auth provider. Here's an example for React Router:

import ReactDOM from 'react-dom'

// First we'll create a custom history
import { createBrowserHistory } from 'history'

import { Router, Route, Link } from 'react-router-dom'
import { TenseiAuthProvider } from '@tensei/react-auth'

// Create custom history object
export const history = createBrowserHistory()


// Create a custom redirect callback. 
const onRedirectCallback = (path: string) => history.replace(path)

ReactDOM.render(
    <TenseiAuthProvider onRedirectCallback={onRedirectCallback}>
        {/* Pass custom history to router */}
        <Router history={history}>
            {/* register your routes */}
        </Router>
    </TenseiAuthProvider>,
    document.querySelector('#app')
)

With the above example, all redirects will be done using history.replace, and there won't be any page refreshes.