In this final build sprint, you'll complete the most challenging ticket yet, bringing your project to completion.
Your third ticket focuses on implementing authentication using Auth0. This popular identity management platform will enable you to add secure login functionality to your application.
The first step is to create an Auth0 account and configure your application:
// Key details you'll need from Auth0 dashboard: - Domain - Client ID - Callback URLs - Logout URLs - Web Origins
npm install @auth0/auth0-react # or yarn add @auth0/auth0-react
import { Auth0Provider } from '@auth0/auth0-react'; import { useNavigate } from 'react-router-dom'; export const Auth0ProviderWithConfig = ({ children }) => { const navigate = useNavigate(); const domain = import.meta.env.VITE_AUTH0_DOMAIN; const clientId = import.meta.env.VITE_AUTH0_CLIENT_ID; const onRedirectCallback = (appState) => { navigate(appState?.returnTo || window.location.pathname); }; return ( <Auth0Provider domain={domain} clientId={clientId} authorizationParams={{ redirect_uri: window.location.origin }} onRedirectCallback={onRedirectCallback} > {children} </Auth0Provider> ); };
# .env file VITE_AUTH0_DOMAIN=your-domain.auth0.com VITE_AUTH0_CLIENT_ID=your-client-id
Note: When using Vite, environment variables must start with VITE_
to be exposed to the client-side code.
import React from 'react'; import { useAuth0 } from '@auth0/auth0-react'; export const LoginButton = () => { const { loginWithRedirect, isAuthenticated } = useAuth0(); return !isAuthenticated && ( <button onClick={() => loginWithRedirect()} className="px-4 py-2 text-white bg-blue-600 rounded hover:bg-blue-700 transition-colors" > Log In </button> ); }; export const LogoutButton = () => { const { logout, isAuthenticated } = useAuth0(); return isAuthenticated && ( <button onClick={() => logout({ returnTo: window.location.origin })} className="px-4 py-2 text-white bg-red-600 rounded hover:bg-red-700 transition-colors" > Log Out </button> ); };
Since your project is using Tailwind CSS, you can leverage its utility classes to style your Auth0 components. Here's how you might style the Profile page:
import React from 'react'; import { useAuth0 } from '@auth0/auth0-react'; const Profile = () => { const { user, isAuthenticated, isLoading } = useAuth0(); if (isLoading) { return <div className="flex justify-center items-center p-8"> <div className="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-blue-500"></div> </div>; } return ( isAuthenticated && ( <div className="max-w-2xl mx-auto bg-white shadow-lg rounded-lg overflow-hidden my-4"> <div className="flex items-center p-6 bg-gray-100"> <img src={user.picture} alt={user.name} className="h-20 w-20 rounded-full object-cover mr-4 shadow" /> <h2 className="text-2xl font-semibold text-gray-800">{user.name}</h2> </div> <div className="p-6 border-t border-gray-200"> <p className="text-gray-700 mb-2"> <span className="font-bold">Email:</span> {user.email} </p> {user.nickname && ( <p className="text-gray-700 mb-2"> <span className="font-bold">Nickname:</span> {user.nickname} </p> )} {user.sub && ( <p className="text-gray-700 mb-2"> <span className="font-bold">Auth0 ID:</span> <span className="text-sm font-mono bg-gray-100 p-1 rounded">{user.sub}</span> </p> )} {user.updated_at && ( <p className="text-gray-700"> <span className="font-bold">Last Updated:</span> {new Date(user.updated_at).toLocaleDateString()} </p> )} </div> </div> ) ); };
For the navigation, you can style it with Tailwind like this:
import { useAuth0 } from '@auth0/auth0-react'; import { Link } from 'react-router-dom'; import { LoginButton, LogoutButton } from './auth-buttons'; const Navigation = () => { const { isAuthenticated } = useAuth0(); return ( <nav className="bg-gray-800 text-white p-4"> <div className="container mx-auto flex justify-between items-center"> <div className="text-xl font-bold">Your App</div> <ul className="flex space-x-6"> <li><Link to="/" className="hover:text-blue-300 transition-colors">Home</Link></li> <li><Link to="/about" className="hover:text-blue-300 transition-colors">About</Link></li> {isAuthenticated && ( <li><Link to="/profile" className="hover:text-blue-300 transition-colors">Profile</Link></li> )} <li> {isAuthenticated ? <LogoutButton /> : <LoginButton />} </li> </ul> </div> </nav> ); };
import React from 'react'; import { useAuth0 } from '@auth0/auth0-react'; const Profile = () => { const { user, isAuthenticated, isLoading } = useAuth0(); if (isLoading) { return <div>Loading...</div>; } return ( isAuthenticated && ( <div className="profile-container"> <div className="profile-header"> <img src={user.picture} alt={user.name} /> <h2>{user.name}</h2> </div> <div className="profile-details"> <p><strong>Email:</strong> {user.email}</p> {user.nickname && ( <p><strong>Nickname:</strong> {user.nickname}</p> )} {user.sub && ( <p><strong>Auth0 ID:</strong> {user.sub}</p> )} {user.updated_at && ( <p><strong>Last Updated:</strong> {new Date(user.updated_at).toLocaleDateString()}</p> )} </div> </div> ) ); };
Create a wrapper component that only allows authenticated users to access certain routes:
import { useAuth0 } from '@auth0/auth0-react'; import { Navigate } from 'react-router-dom'; export const ProtectedRoute = ({ children }) => { const { isAuthenticated, isLoading } = useAuth0(); if (isLoading) { return ( <div className="flex justify-center items-center h-screen"> <div className="animate-spin rounded-full h-16 w-16 border-t-4 border-blue-500"></div> </div> ); } return isAuthenticated ? children : <Navigate to="/" replace />; };
Use it in your route configuration:
<Routes> <Route path="/" element={<Home />} /> <Route path="/profile" element={ <ProtectedRoute> <Profile /> </ProtectedRoute> } /> </Routes>
Update your navigation menu to conditionally show options based on authentication status:
import { useAuth0 } from '@auth0/auth0-react'; import { LoginButton, LogoutButton } from './auth-buttons'; const Navigation = () => { const { isAuthenticated } = useAuth0(); return ( <nav> <ul> <li><Link to="/">Home</Link></li> <li><Link to="/about">About</Link></li> {isAuthenticated && ( <li><Link to="/profile">Profile</Link></li> )} <li> {isAuthenticated ? <LogoutButton /> : <LoginButton />} </li> </ul> </nav> ); };
When implementing Auth0, remember these important security practices:
As you reach the advanced stages of your project, learning about sophisticated React patterns can help you create more maintainable code.
// Example of Compound Component Pattern import React, { createContext, useContext, useState } from 'react'; // Create a context for the accordion const AccordionContext = createContext(); // Main Accordion component export function Accordion({ children, defaultIndex = 0 }) { const [activeIndex, setActiveIndex] = useState(defaultIndex); const toggleItem = (index) => { setActiveIndex(index === activeIndex ? null : index); }; return ( <AccordionContext.Provider value={{ activeIndex, toggleItem }}> <div className="accordion"> {children} </div> </AccordionContext.Provider> ); } // AccordionItem component export function AccordionItem({ children, index }) { const { activeIndex, toggleItem } = useContext(AccordionContext); const isActive = activeIndex === index; return ( <div className={`accordion-item ${isActive ? 'active' : ''}`}> {React.Children.map(children, child => { return React.cloneElement(child, { isActive, index, toggleItem }); })} </div> ); } // AccordionHeader component export function AccordionHeader({ children, isActive, index, toggleItem }) { return ( <div className="accordion-header" onClick={() => toggleItem(index)} > {children} <span className="accordion-icon">{isActive ? '▲' : '▼'}</span> </div> ); } // AccordionPanel component export function AccordionPanel({ children, isActive }) { return ( <div className={`accordion-panel ${isActive ? 'show' : 'hide'}`}> {children} </div> ); } // Usage: // <Accordion> // <AccordionItem index={0}> // <AccordionHeader>Section 1</AccordionHeader> // <AccordionPanel>Content for section 1</AccordionPanel> // </AccordionItem> // <AccordionItem index={1}> // <AccordionHeader>Section 2</AccordionHeader> // <AccordionPanel>Content for section 2</AccordionPanel> // </AccordionItem> // </Accordion>
As you approach the end of the project, it's important to prepare your application for deployment.
# For Create React App npm run build # For Next.js next build && next export
# .env.development VITE_API_URL=http://localhost:5000/api VITE_AUTH0_DOMAIN=your-domain.auth0.com VITE_AUTH0_CLIENT_ID=your-development-client-id # .env.production VITE_API_URL=https://your-production-api.com/api VITE_AUTH0_DOMAIN=your-domain.auth0.com VITE_AUTH0_CLIENT_ID=your-production-client-id
class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(error) { return { hasError: true }; } componentDidCatch(error, errorInfo) { console.error("Error caught by boundary:", error, errorInfo); // You can also log to an error reporting service here } render() { if (this.state.hasError) { return this.props.fallback || <h1>Something went wrong.</h1>; } return this.props.children; } }
# Example GitHub Actions workflow name: Deploy on: push: branches: [ main ] jobs: build-and-deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Install Dependencies run: npm install - name: Build run: npm run build - name: Deploy to Netlify uses: netlify/actions/cli@master with: args: deploy --dir=build --prod env: NETLIFY_AUTH_TOKEN: $\{\{ secrets.NETLIFY_AUTH_TOKEN \}\} NETLIFY_SITE_ID: $\{\{ secrets.NETLIFY_SITE_ID \}\}
Before deploying your application, ensure you've:
Add final touches to make your project stand out and ensure it's ready for presentation.