Frequently Asked Questions

Authentication & React Integration

How can I add authentication to a React application using Hygraph?

You can add authentication to a React application by setting up a backend server (such as Node.js with Express) that connects to Hygraph as the headless CMS. The backend manages user data and authentication logic, including JWT-based bearer token authentication. On the frontend, you use React (e.g., Create React App) to interact with the backend APIs for sign-up, sign-in, and user verification. The tutorial on the Hygraph blog provides a step-by-step guide, including code samples and a demo repository: react-basic-auth. [Source: Hygraph Blog]

What are the main components required for implementing authentication in a React app with Hygraph?

The main components include:

[Source: Hygraph Blog]

How does JWT-based authentication work in the Hygraph React authentication tutorial?

JWT-based authentication involves issuing a JSON Web Token upon successful sign-up or sign-in. The backend signs the token with a secret and sends it to the client. The client stores the token (e.g., in local storage) and includes it in requests to protected APIs. The backend verifies the token on each request to authenticate the user. [Source: Hygraph Blog]

What are some best practices or advanced features to consider for authentication in production React apps?

For production apps, consider implementing advanced features such as Single Sign-On (SSO), OAuth flows, fine-grained role-based authorization, token expiry, and enhanced security measures. Managing authentication and authorization from scratch can be complex, so many teams use third-party services like AWS Cognito or Auth0 for advanced requirements. [Source: Hygraph Blog]

Features & Capabilities

Does Hygraph provide an API for managing content and authentication?

Yes, Hygraph provides a powerful GraphQL API for fetching and managing content efficiently. While Hygraph itself does not provide a built-in authentication service, it can be integrated with custom authentication logic or third-party providers. Learn more at the Hygraph API Reference.

What integrations does Hygraph support?

Hygraph supports a wide range of integrations, including hosting and deployment (Netlify, Vercel), eCommerce (BigCommerce, commercetools, Shopify), localization (Lokalise, Crowdin, EasyTranslate, Smartling), digital asset management (Aprimo, AWS S3, Bynder, Cloudinary, Mux, Scaleflex Filerobot), personalization and AB testing (Ninetailed), artificial intelligence (AltText.ai), and more. For a full list, visit the Hygraph Integrations page.

Where can I find technical documentation for Hygraph?

Comprehensive technical documentation for Hygraph is available at https://hygraph.com/docs. It covers everything from getting started to advanced integrations and API usage.

Security & Compliance

What security and compliance certifications does Hygraph have?

Hygraph is SOC 2 Type 2 compliant, ISO 27001 certified, and GDPR compliant. These certifications ensure enterprise-grade security and data protection for users. For more details, visit the Hygraph Security Features page.

What security features does Hygraph offer?

Hygraph offers robust security features including SSO integrations, audit logs, encryption at rest and in transit, and sandbox environments to protect sensitive data and meet regulatory standards. [Source: Hygraph Security Features]

Pricing & Plans

What is Hygraph's pricing model?

Hygraph offers a free forever Hobby plan, a Growth plan starting at $199/month, and custom Enterprise plans. For the latest details, visit the Hygraph Pricing page. [Source: Hygraph Pricing]

Use Cases & Benefits

Who can benefit from using Hygraph?

Hygraph is ideal for developers, IT decision-makers, content creators, project/program managers, agencies, solution partners, and technology partners. It is especially beneficial for modern software companies, enterprises modernizing their tech stack, and brands scaling across geographies or re-platforming from traditional solutions. [Source: ICPVersion2_Hailey.pdf]

What problems does Hygraph solve for businesses?

Hygraph addresses operational pains (like reliance on developers for content updates, outdated tech stacks, and clunky user experiences), financial pains (high operational costs, slow speed-to-market, expensive maintenance, scalability challenges), and technical pains (boilerplate code, overwhelming queries, evolving schemas, cache problems, and OpenID integration challenges). [Source: Hygraph Product Page]

What business impact can customers expect from using Hygraph?

Customers can expect significant time savings, streamlined workflows, faster speed-to-market, and enhanced customer experience through consistent and scalable content delivery. These benefits help businesses modernize their tech stack and achieve operational efficiency. [Source: ICPVersion2_Hailey.pdf]

Customer Experience & Support

How easy is it to get started with Hygraph?

Hygraph is designed for ease of use, with customers reporting that it is 'super easy to set up and use.' Even non-technical users can start using it right away. You can sign up for a free account and access onboarding guides, documentation, and video tutorials. [Source: Customer Feedback]

What support and training does Hygraph offer?

Hygraph provides 24/7 support via chat, email, and phone. Enterprise customers receive dedicated onboarding and expert guidance. All users have access to detailed documentation, video tutorials, and a community Slack channel. [Source: Hygraph Contact Page]

Performance & Technical Requirements

How does Hygraph optimize content delivery performance?

Hygraph is optimized for rapid content delivery, which improves user experience, engagement, and search engine rankings. Fast content distribution reduces bounce rates and increases conversions. [Source: Performance Checklist]

Customer Success Stories

Can you share specific customer success stories using Hygraph?

Yes. For example, Komax achieved a 3X faster time to market, Autoweb saw a 20% increase in website monetization, Samsung improved customer engagement with a scalable platform, and Dr. Oetker enhanced their digital experience using MACH architecture. More stories are available at the Hygraph Product Page.

Blog & Community

Where can I find more tutorials and updates from Hygraph?

You can find developer tutorials, updates, and guides on the Hygraph Blog. The blog covers topics like authentication, content modeling, and integration best practices.

Velocity at Scale: Join the Launch of Hygraph’s Latest AI Innovations

How to add authentication to React applications

In this article, we’ll explore with the help of a demo, how to set up authentication in a Create React App.
Aagam Vadecha

Last updated by Aagam 

Nov 04, 2024

Originally written by Aagam

How to add authentication to React applications

Authentication and Authorization are crucial for any software to be built today. In this article, we’ll explore with the help of a demo, how to set up authentication in a Create React App. We will implement a basic straightforward JWT-based bearer token authentication.

The components of our setup will be like:

It is completely fine to choose your own backend server and database as well. The code for the final frontend and backend apps can be found here.

#Setting Up The Backend

Hygraph Schema

To set up the base, we should begin by creating our User schema in Hygraph dashboard. We will keep fields like firstname, lastname, email, and password in the schema, it will look something like this:

UserSchema.png

Once this schema is configured, we can move on to build the backend Node.js Express API.

Node.js Backend Application

Base Setup

Do an npm initin a fresh folder to create a new application and install the following dependencies using npm install

We will be using bcryptjsto hash passwords, expressand corsto manage the backend API, dotenvto support environment variables via a .env file, jsonwebtokento sign, and decode the jwt-tokens for authentication, and finally graphql, graphql-requestto fetch data from Hygraph GraphQL API.

Let's begin with some base backend server setup. Please add the following files

.env

JWT_SECRET=SUPERSECRET
JWT_EXPIRES_IN=1 hour
HYGRAPH_URL=VALUE
HYGRAPH_PERMANENTAUTH_TOKEN=VALUE

index.js

import 'dotenv/config';
import app from './app.js';
const port = process.env.PORT || 4000;
app.listen(port, () => {
console.log(`Backend API Ready On Port: ${port}`);
});

app.js

import express from 'express';
import authRoutes from './routes/authRoutes.js';
import cors from 'cors'
const app = express();
app.use(express.json());
app.use(cors());
app.use(authRoutes);
export default app;

graphql/client.js

import { GraphQLClient } from 'graphql-request';
const { HYGRAPH_URL, HYGRAPH_PERMANENTAUTH_TOKEN } = process.env;
const client = new GraphQLClient(HYGRAPH_URL, {
headers: {
Authorization: `Bearer ${HYGRAPH_PERMANENTAUTH_TOKEN}`,
},
});
export default client;

graphql/mutations.js

import { gql } from 'graphql-request';
export const CreateNextUserMutation = gql`
mutation CreateNextUser($userData: NextUserCreateInput!) {
createNextUser(data: $userData) {
id
email
}
}
`;
export const GetUserByEmailQuery = gql`
query getUserByEmailQuery($email: String!) {
nextUser(where: { email: $email }, stage: DRAFT) {
id
email
firstname
lastname
password
}
}
`;

In a backend application we generally have Routes, Controllers and Services. Let us use the same framework to create three APIs that we will need, one to Sign Up, one to Sign In, and one to verify or get the user from an issued access token.

Routes

Let us first add the routes

routes/authRoutes.js

import express from 'express';
import AuthController from '../controllers/authController.js';
const router = express.Router();
const authController = new AuthController();
router.post('/auth/signup', (req, res) => authController.signup(req, res));
router.post('/auth/signin', (req, res) => authController.signin(req, res));
router.get('/auth/me', (req, res) => authController.getCurrentUser(req, res));
export default router;

We have defined three routes with the endpoints - /auth/signup, /auth/signin, and /auth/me respectively.

Controller

Now let us add the controller for these three routes

controllers/authController.js

import AuthService from "../services/authService.js";
class AuthController {
constructor() {
this.authService = new AuthService();
}
async signup(req, res) {
try {
const { email, password, firstname, lastname } = req.body;
if (!email || !password || !firstname || !lastname) {
res.status(400).end();
return;
}
const { user, token } = await this.authService.signup({
email,
password,
firstname,
lastname,
});
res.send({ user, token });
} catch (err) {
console.error("POST auth/signup, Something Went Wrong:", err);
res.status(400).send({ error: true, message: err.message });
}
}
async signin(req, res) {
try {
const { email, password } = req.body;
if (!email || !password) {
res.status(400).end();
return;
}
const token = await this.authService.signin(email, password);
res.status(200).json({ token });
} catch (err) {
console.error("POST auth/signin, Something Went Wrong:", err);
res.status(400).send({ error: true, message: err.message });
}
}
async getCurrentUser(req, res) {
const defaultReturnObject = { authenticated: false, user: null };
try {
const token = String(req.headers.authorization?.replace("Bearer ", ""));
const user = await this.authService.getCurrentUser(token);
res.status(200).json({ authenticated: true, user });
} catch (err) {
console.error("GET auth/me, Something Went Wrong:", err);
res.status(400).json(defaultReturnObject);
}
}
}
export default AuthController;

In all three functions of the class - signup, signin, getCurrentUser, we have done these steps:

  1. Destructure input from the request.
  2. Validate the input.
  3. Call the service function to run the respective logic.
  4. Get response from service and send it back to the client.
  5. Catch and throw any run time errors.

Service

Finally, let us create the service file step by step and the first step would be to create the skeleton for our service file where the actual authentication logic will be placed for each API.

services/authService.js

import bcrypt from "bcryptjs";
import jwt from "jsonwebtoken";
import gqlClient from "../graphql/gqlClient.js";
import {
CreateNextUserMutation,
GetUserByEmailQuery,
} from "../graphql/mutations.js";
const { JWT_SECRET, JWT_EXPIRES_IN } = process.env;
class AuthService {
async signup(signupRequest) {
// SIGNUP LOGIC
}
async signin(email, password) {
// SIGN IN LOGIC
}
async getCurrentUser(token) {
// GET CURRENT USER LOGIC
}
}
export default AuthService;

Now let us add and understand individual functions step by step, starting with the sign up function:

// SIGN UP LOGIC
async signup(signupRequest) {
const { email, password, firstname, lastname } = signupRequest;
const hashedPassword = await bcrypt.hash(password, 8);
const userData = {
email,
password: hashedPassword,
firstname,
lastname,
};
const response = await gqlClient.request(CreateNextUserMutation, {
userData,
});
if (!response?.createNextUser) {
throw new Error("CreateUser Failed");
}
const token = jwt.sign({ user: response.createNextUser }, JWT_SECRET, {
expiresIn: JWT_EXPIRES_IN,
});
return { user: response.createNextUser, token };
}

For Sign Up, we get the new user details from the controller, then we hash the password and save the user in the database, finally we create a jwt-token with the user details and send the token back to the client.

Moving on to the sign in functionality, here we will accept email and password, get the user details from our database, and compare the given and stored password hash with bcrypt. We throw an error if the passwords do not match, else we create a fresh jwt token and send it back to the client.

// SIGN IN LOGIC
async signin(email, password) {
const getUserResponse = await gqlClient.request(GetUserByEmailQuery, {
email,
});
const { nextUser } = getUserResponse;
if (!nextUser) {
throw new Error("Invalid Email Or Password");
}
const isMatch = await bcrypt.compare(password, nextUser.password);
if (!isMatch) {
throw new Error("Invalid Email Or Password");
}
const token = jwt.sign(
{
id: nextUser.id,
email: nextUser.email,
},
JWT_SECRET,
{ expiresIn: JWT_EXPIRES_IN }
);
return token;
}

Finally, one API to get the currently authenticated user details from an existing access token. It will accept and verify the access token, and send the user object back if the token is valid. This idea of this getCurrentUser function can be expanded further as a middleware to verify your user before running the logic for all your other protected APIs in the backend.

// GET CURRENT USER LOGIC
async getCurrentUser(token) {
const decoded = jwt.verify(token, JWT_SECRET);
const getUserResponse = await gqlClient.request(GetUserByEmailQuery, {
email: decoded.email,
});
const { nextUser } = getUserResponse;
if (!nextUser) {
throw new Error("User not found");
}
delete nextUser.password;
return nextUser;
}

Now go to the root of this setup and start the application with node index.js, you should be able to see that the backend app is now running, and with this our Authentication backend setup is complete!

#Setting Up The Frontend

Base Setup

To start a new React application using Create React App move to a fresh folder and type npx create-react-app frontend. To this app, add axios and react-router-dom dependencies. We’ll be using tailwind css for this app but feel free to use your own CSS/UI framework. To begin with a clean slate, clean up the create-react-app boilerplate css and test files.

The strategy here to handle authentication is that when we hit the backend SignIn API, it will give us an access token, we will save that token somewhere on the client side. Then on every route visit in the frontend we will just check if the token is present on the client side and if we are able to get the user by calling the GET /me API. We will write this logic inside a custom react hook for reusability.

First, please go through self explanatory code files and add them in your app as well

src/utils/constants.js

src/lib/common.js

Now we will add a custom hook to check for the Authenticated user. This hook can be used inside any react component to get the Current Authenticated User.

src/lib/customHooks.js

import { useState, useEffect } from 'react';
import { getAuthenticatedUser } from './common';
import { APP_ROUTES } from '../utils/constants';
import { useNavigate } from 'react-router-dom';
export function useUser() {
const [user, setUser] = useState(null);
const [authenticated, setAutenticated] = useState(false);
const navigate = useNavigate();
useEffect(() => {
async function getUserDetails() {
const { authenticated, user } = await getAuthenticatedUser();
if (!authenticated) {
navigate(APP_ROUTES.SIGN_IN);
return;
}
setUser(user);
setAutenticated(authenticated);
}
getUserDetails();
}, []);
return { user, authenticated };
}

Components

Now we can have the main app with Routes and three simple components namely Sign In, Sign Up and a protected Route Dashboard that will be only accessible if the user is logged in.

src/App.js

import { BrowserRouter, Route, Routes, Navigate } from 'react-router-dom';
import Dashboard from './components/Dashboard';
import SignIn from './components/SignIn';
import SignUp from './components/SignUp';
import { APP_ROUTES } from './utils/constants';
function App() {
return (
<BrowserRouter>
<Routes>
<Route exact path="/" element={<Navigate to={APP_ROUTES.DASHBOARD} />} />
<Route path={APP_ROUTES.SIGN_UP} exact element={<SignUp />} />
<Route path={APP_ROUTES.SIGN_IN} element={<SignIn />} />
<Route path={APP_ROUTES.DASHBOARD} element={<Dashboard />} />
</Routes>
</BrowserRouter>
);
}
export default App;

Below are the SingUp and SignIn components - full component code with markup can be found here

components/SignUp.jsx

import React from 'react';
import axios from 'axios';
import { useState } from 'react';
import { API_ROUTES, APP_ROUTES } from '../utils/constants';
import { Link, useNavigate } from 'react-router-dom';
const SignUp = () => {
const navigate = useNavigate()
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [firstname, setFirstname] = useState('');
const [lastname, setLastname] = useState('');
const [isLoading, setIsLoading] = useState(false);
const signUp = async () => {
try {
setIsLoading(true);
const response = await axios({
method: 'POST',
url: API_ROUTES.SIGN_UP,
data: {
email,
password,
firstname,
lastname
}
});
if (!response?.data?.token) {
console.log('Something went wrong during signing up: ', response);
return;
}
navigate(APP_ROUTES.SIGN_IN);
}
catch (err) {
console.log('Some error occured during signing up: ', err);
}
finally {
setIsLoading(false);
}
};
return (
// MARKUP
);
}
export default SignUp;

SignUp.png

components/SignIn.jsx

import React from 'react';
import axios from 'axios';
import { useState } from 'react';
import { API_ROUTES, APP_ROUTES } from '../utils/constants';
import { Link, useNavigate } from 'react-router-dom';
import { useUser } from '../lib/customHooks';
import { storeTokenInLocalStorage } from '../lib/common';
const SignIn = () => {
const navigate = useNavigate();
const { user, authenticated } = useUser();
if (user || authenticated) {
navigate(APP_ROUTES.DASHBOARD)
}
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [isLoading, setIsLoading] = useState(false);
const signIn = async () => {
try {
setIsLoading(true);
const response = await axios({
method: 'post',
url: API_ROUTES.SIGN_IN,
data: {
email,
password
}
});
if (!response?.data?.token) {
console.log('Something went wrong during signing in: ', response);
return;
}
storeTokenInLocalStorage(response.data.token);
navigate(APP_ROUTES.DASHBOARD)
}
catch (err) {
console.log('Some error occured during signing in: ', err);
}
finally {
setIsLoading(false);
}
};
return (
// MARKUP
);
}
export default SignIn;

SignIn.png

That’s it! Try signing up a user with the Sign Up page, it will call our backend signup API and register the user in the database. After that, you can sign in using the above component. Once signed in, we store the token in local storage and then we redirect the user to some page that requires a user to be authenticated.

Let us build the Dashboard component which will be a protected route, we will make use of the useUser() custom hook that we made earlier to get the authenticated user.

components/Dashboard.jsx

import React from 'react';
import { useUser } from '../lib/customHooks';
const Dashboard = () => {
const { user, authenticated } = useUser();
if (!user || !authenticated) {
return <div className="p-16 bg-gray-800 h-screen">
<div className="text-2xl mb-4 font-bold text-white">Dashboard</div>
<div className="ml-2 w-8 h-8 border-l-2 rounded-full animate-spin border-white" />
</div>;
}
return (
<div className="p-16 bg-gray-800 h-screen">
<div className="text-2xl mb-4 font-bold text-white"> Dashboard </div>
{
user &&
<div className='text-white'>
<div className="text-lg text-bold mb-2"> User Details </div>
<div className="flex">
<div className="w-24 font-medium">
<div> Email : </div>
<div> Firstname : </div>
<div> Lastname : </div>
</div>
<div>
<div> {user.email} </div>
<div> {user.firstname} </div>
<div> {user.lastname} </div>
</div>
</div>
</div>
}
</div>
);
}
export default Dashboard;

Dashboard.png

If you delete the token from local storage and try to go to route /dashboardyou’ll be redirected to /signin the useUser() hook handles that redirection.

Well done, a complete application right from the database all the way to a React Frontend with Authentication is ready! If you’re looking to build a robust application that scales well in production, you might want to take a look how a create react app differs from Next.js, the production framework for React!

#Conclusion

In this article, we went through the level one of building Authentication for full stack applications. We set up the database with a Hygraph schema, connected our backend app to it, created three APIs and connected three frontend components to those APIs. This was a very minimal and basic authentication setup, and something that every developer should have an idea about. This can be expanded further to support a number of use cases like fine grained authorization rules per API, expiry of tokens, security features can be further improved, and more.

However, enterprise application’s authentication & authorization requirements can become more complex over time. Managing multiple logins, revoking access, SSO Logins, OAuth flows, fine grained role based authorization rules, fingerprints, all these advanced features are required by many software products. In reality, it is quite difficult to build and manage Authentication and Authorization from scratch. OAuth flows can get tricky, you have to keep all your Auth API up-to-date with the latest standards and look out for all vulnerabilities in your system. If you build your own Auth, you have to maintain it responsibly as security issues are not something you can stall. That is why many teams who want to focus more on building their actual product and not be tangled with Authentication and Authorization workflows opt for services like AWS Cognito or Auth0.

Blog Authors

Share with others

Sign up for our newsletter!

Be the first to know about releases and industry news and insights.