Protected Routes with AWS Amplify using React context
Asked Answered
S

1

6

I am migrating an app from Firebase to AWS Amplify. I want to create a React context which will provide route protection if the user is not logged in.

For example, my Auth.js file:

import React, { useEffect, useState, createContext } from 'react'
import fire from './firebase'

export const AuthContext = createContext()

export const AuthProvider = ({ children }) => {
    const [currentUser, setCurrentUser] = useState(null)

    useEffect(() => {
        fire.auth().onAuthStateChanged(setCurrentUser)
    }, [])

    return (
        <AuthContext.Provider value={{ currentUser }}>
            {children}
        </AuthContext.Provider>
    )
}

And my App.js file:

import * as React from 'react'
import { BrowserRouter, Switch, Route } from 'react-router-dom'

import Navbar from './components/navbar/navbar'
import Home from './routes/Home'
import Register from './routes/Register'

import Footer from './components/footer/Footer'

import AlertProvider from './components/notification/NotificationProvider'
import MyAlert from './components/notification/Notification'

import { AuthProvider } from './Auth'
import PrivateRoute from './PrivateRoute'

const App = () => {
    return (
        <AuthProvider>
            <BrowserRouter>
                <AlertProvider>
                    <div className="app">
                        <Navbar />
                        <MyAlert />
                        <Switch>
                            <Route path="/" exact component={Home} />
                            <Route
                                path="/register"
                                exact
                                component={Register}
                            />
                            <Route
                                path="/forgot-password"
                                render={(props) => <div>Forgot Password</div>}
                            />
                            <Route path="*" exact={true} component={Home} />
                        </Switch>
                        <Footer />
                    </div>
                </AlertProvider>
            </BrowserRouter>
        </AuthProvider>
    )
}

export default App

This all works fine.

How would I do something similar with AWS Amplify? Essentially how would I create a Auth.js file that would wrap around my routes and give them a user context (which would update when the authentication status for the user is changed).

Thanks!

Sailcloth answered 12/1, 2021 at 21:49 Comment(2)
Perhaps using context for this is unnecessary and instead you could have a HOC component that wraps around your protected routes and checks if the user is authenticated from there and then allows them to use the route or if not redirect them to a specified path.Robbert
Thanks, would you have a link to a code example? I am new to this and would definitely appreciate some sort of code that I can digest.Sailcloth
R
7

You can achieve this by setting up a custom protectedRoute HOC that will be used to protect any route that requires authentication. It will check if the user is signed-in and if the user is not signed-in then it will re-direct them to a specified route.

protectedRoute.js

import React, { useEffect } from 'react'
import { Auth } from 'aws-amplify'

const protectedRoute = (Comp, route = '/profile') => (props) => {
  async function checkAuthState() {
    try {
      await Auth.currentAuthenticatedUser()
    } catch (err) {
      props.history.push(route)
    }
  }
  useEffect(() => {
    checkAuthState()
  })
  return <Comp {...props} />
}

export default protectedRoute

You can specify the default route or another route like the following:

// default redirect route
export default protectedRoute(Profile)

// custom redirect route
export default protectedRoute(Profile, '/sign-in')

You could also use the pre-built HOC from aws-amplify called withAuthenticator and that provides the UI as well as checking the users authentication status.

Sample use case for a profile page:

import React, { useState, useEffect } from 'react'
import { Button } from 'antd'
import { Auth } from 'aws-amplify'
import { withAuthenticator } from 'aws-amplify-react'
import Container from './Container'

function Profile() {
  useEffect(() => {
    checkUser()
  }, [])
  const [user, setUser] = useState({}) 
  async function checkUser() {
    try {
      const data = await Auth.currentUserPoolUser()
      const userInfo = { username: data.username, ...data.attributes, }
      setUser(userInfo)
    } catch (err) { console.log('error: ', err) }
  }
  function signOut() {
    Auth.signOut()
      .catch(err => console.log('error signing out: ', err))
  }
  return (
    <Container>
      <h1>Profile</h1>
      <h2>Username: {user.username}</h2>
      <h3>Email: {user.email}</h3>
      <h4>Phone: {user.phone_number}</h4>
      <Button onClick={signOut}>Sign Out</Button>
    </Container>
  );
}

export default withAuthenticator(Profile)

The routing for both would be the same and below I have linked a sample that I have used for both.:

import React, { useState, useEffect } from 'react'
import { HashRouter, Switch, Route } from 'react-router-dom'

import Nav from './Nav'
import Public from './Public'
import Profile from './Profile'
import Protected from './Protected'

const Router = () => {
  const [current, setCurrent] = useState('home')
  useEffect(() => {
    setRoute()
    window.addEventListener('hashchange', setRoute)
    return () =>  window.removeEventListener('hashchange', setRoute)
  }, [])
  function setRoute() {
    const location = window.location.href.split('/')
    const pathname = location[location.length-1]
    setCurrent(pathname ? pathname : 'home')
  }
  return (
    <HashRouter>
      <Nav current={current} />
      <Switch>
        <Route exact path="/" component={Public}/>
        <Route exact path="/protected" component={Protected} />
        <Route exact path="/profile" component={Profile}/>
        <Route component={Public}/>
      </Switch>
    </HashRouter>
  )
}

export default Router
Robbert answered 14/1, 2021 at 3:15 Comment(3)
Thank you! That is a great response and makes it much clearer for meSailcloth
Glad it helped you out!Robbert
@yudhiesh, what I am not understanding is your checkAuthState function. you're sending to profile only if the auth fails. shouldn't you send to some route when it passes too?Honky

© 2022 - 2024 — McMap. All rights reserved.