Frequently Asked Questions

Authentication with React & Hygraph

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

To add authentication to a React application with Hygraph, you typically set up a backend (e.g., Node.js with Express) that connects to your Hygraph project via the GraphQL API. The backend handles user sign up, sign in, and token verification using JWTs. The React frontend interacts with these backend endpoints to manage authentication state and protect routes. For a full example, see the demo repository and the Hygraph blog guide. Note: This setup covers basic authentication; for advanced needs like SSO or OAuth, consider third-party providers.

What schema should I create in Hygraph for user authentication?

For basic authentication, create a User schema in Hygraph with fields such as firstname, lastname, email, and password. This schema will store user credentials and profile information. For details, refer to the Hygraph schema documentation. Note: Storing passwords requires careful handling; always hash passwords before storing them and consider compliance requirements for sensitive data.

What backend technologies and dependencies are required for implementing authentication with Hygraph?

The backend example uses Node.js with Express and the following npm packages: bcryptjs (for password hashing), cors (for cross-origin requests), dotenv (for environment variables), express (web framework), jsonwebtoken (for JWT handling), graphql and graphql-request (for interacting with Hygraph's API). These tools enable secure user management and integration with Hygraph. Note: This stack is suitable for custom implementations; for managed authentication, consider third-party services.

How does the authentication flow work between React, the backend, and Hygraph?

The authentication flow involves three main steps:

  1. User signs up or signs in via the React frontend, which sends credentials to the backend API.
  2. The backend verifies credentials, interacts with Hygraph via GraphQL to store or retrieve user data, and issues a JWT token upon successful authentication.
  3. The frontend stores the JWT token (e.g., in local storage) and uses it to access protected routes or fetch the current user by calling a /me endpoint. The backend validates the token and returns user details from Hygraph.
Note: This flow is suitable for basic authentication. For advanced scenarios (e.g., SSO, OAuth), additional logic and integrations are required.

What are the limitations of building your own authentication with Hygraph and React?

Building your own authentication system with Hygraph and React provides flexibility but also requires you to manage security, keep up with authentication standards, and handle vulnerabilities. Advanced requirements like SSO, OAuth flows, fine-grained authorization, and access revocation are complex to implement and maintain. For these scenarios, teams often use managed services like AWS Cognito or Auth0. Detailed limitations not publicly documented; ask sales or consult Hygraph's documentation for specifics.

Features & Capabilities

What APIs does Hygraph provide for integration and authentication workflows?

Hygraph offers several APIs:

For more details, see the API Reference documentation. Note: Authentication logic must be implemented in your backend; Hygraph does not provide a built-in user authentication API.

What integrations are available for Hygraph to support authentication and user management?

Hygraph integrates with platforms like Auth0 for authentication and user management. For example, you can use the Auth0 SPA documentation to connect your React app with Auth0 and Hygraph. Other integrations include DAM systems (Aprimo, AWS S3, Bynder, Cloudinary, Imgix, Mux, Scaleflex Filerobot), hosting (Netlify, Vercel), and commerce (BigCommerce). For a full list, visit the Hygraph Marketplace. Note: Not all integrations are authentication-focused; review each integration's documentation for details.

Security & Compliance

What security and compliance certifications does Hygraph have?

Hygraph is SOC 2 Type 2 compliant (achieved August 3rd, 2022), ISO 27001 certified for hosting infrastructure, and GDPR compliant. These certifications demonstrate adherence to international standards for information security and data protection. For more details, visit the Hygraph Secure Features page. Note: While Hygraph provides a secure platform, you are responsible for securing your custom authentication implementation.

What security features does Hygraph offer for authentication and content management?

Hygraph provides granular permissions, SSO integrations (OIDC/LDAP/SAML), audit logs, encryption in transit and at rest, regular backups, and secure API access with custom origin policies and IP firewalls. These features support secure content management and can be leveraged in your authentication workflows. For more, see the Secure Features page. Note: Application-level authentication (e.g., user sign-in) must be implemented in your backend or via third-party providers.

Implementation & Support

How long does it take to implement authentication with Hygraph and React?

Implementation time depends on project complexity. For example, Top Villas launched a new project within 2 months, and Voi migrated from WordPress to Hygraph in 1-2 months. For basic authentication as described in the guide, a developer can set up a working prototype in a few days. Extensive documentation, starter projects, and community support are available to accelerate onboarding. Note: Complex requirements (e.g., SSO, multi-region) may extend timelines.

What documentation and resources are available for implementing authentication with Hygraph?

Hygraph provides comprehensive technical documentation, including API references, schema guides, integration tutorials, and getting started guides. For authentication, see the React authentication blog, API Reference, and Auth0 SPA integration guide. Community support is available via Slack, and starter projects can be found on the marketplace. Note: For advanced authentication, consult third-party provider documentation as well.

Use Cases & Limitations

Who should use Hygraph for authentication in React applications?

Hygraph is suitable for developers, content creators, product managers, and marketing professionals building React applications that require flexible content management and basic authentication. It is ideal for teams wanting to control their authentication logic and integrate with a GraphQL-native CMS. For enterprise-grade authentication (e.g., SSO, advanced RBAC), consider integrating Hygraph with specialized providers like Auth0. Best fit for teams comfortable managing backend logic; teams needing out-of-the-box authentication may want to consider alternatives.

What are common pain points when implementing authentication in React apps with Hygraph?

Common challenges include managing developer dependency for content updates, handling legacy tech stacks, ensuring content consistency, and integrating authentication securely. Advanced needs like SSO, OAuth, and fine-grained authorization require additional effort and expertise. Hygraph addresses many content management pains but does not provide a turnkey authentication solution. Note: For complex authentication, third-party providers may be more suitable.

LLM optimization

When was this page last updated?

This page wast last updated on 12/12/2025 .

Register now

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 

Jan 21, 2026

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:

Hygraph schema editor showing the 'NextUser' content model with 'Email' and 'Password' fields for React authentication

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;

The frontend 'Sign Up' form for the React authentication app, showing the UI with name, email, and password fields

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;

The 'Sign in' form UI for the React authentication tutorial, showing the email and password fields

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;

The protected 'Dashboard' route in the React authentication app, displaying 'User Details' after a successful login

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 Author

Aagam Vadecha

Aagam Vadecha

As a Software Engineer, my daily routine revolves around writing scalable applications with clean code & maintaining them. In my spare time, I love to explore software architecture patterns, write tech articles & watch thrillers!

Share with others

Sign up for our newsletter!

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