Skip to main content

Integrate authentication into React + API

This guide shows how to create a simple React application and secure it with authentication powered by Ory. The guide provides the setup for using Ory Network, but the code can be used with both Ory Network and self-hosted Ory software.

This guide is perfect for you if:

  1. You have React installed.
  2. You want to build an app using React.
  3. You want to give access to your application to signed-in users only.

Before you start, watch this video to see the user flow you're going to implement:

info

You can find the code of the sample application here.

Create React app

First, create a new React project:

npm create vite@latest -- --template react-ts
cd app-name
npm install

Create a new Ory project

  1. Create an Ory account at https://console.ory.sh
  2. Create a new project at https://console.ory.sh/projects/create
  3. Go to your project settings
https://console.ory.sh/projects/<id>/settings

Project settings tab

  1. Note down your project credentials (ID, slug, endpoint)
https://console.ory.sh/projects/<id>/settings

Project credentials

Install Ory CLI and SDK

Follow this guide to install the Ory CLI on your machine.

Run this to install the ory client sdk for your project:

npm i @ory/client-fetch

Why do I need the Ory CLI

The Ory security model uses HTTP cookies to manage sessions, tokens, and cookies. Because of browser security measures like CORS, Ory APIs must be exposed on the same domain as your application. In the case of this example the application runs on your local machine. The cookie domain is localhost.

info

Use either localhost or 127.0.0.1 consistently. Although technically they refer to the same machine, browsers treat them as different cookie domains.

Ory CLI provides a convenient way to configure and manage projects. Additionally, the CLI provides Ory Tunnel - a tool that ensures cookies match the domain your application is currently on.

To make Ory APIs and your application available on the same domain, Ory Tunnel mirrors Ory endpoints and rewrites cookies to match the correct domain. As a result, the domain of the cookies is set correctly to the domain you run the app on instead of <your-project-slug>.projects.oryapis.com.

By using the Tunnel, you can easily connect the application you're developing locally to Ory Network and consume the APIs without additional configuration or self-hosting any Ory services.

tip

To learn more about the Ory Tunnel, read the dedicated section of the Ory CLI documentation.

Run Ory APIs on localhost

In a new terminal window, start the Ory Tunnel:

ory tunnel --project <project-id> http://localhost:5173

This command mirrors Ory APIs on http://localhost:4000. Use that URL as the basePath for the @ory/client-fetch SDK. Obtain your project ID from the Ory Console.

Authenticate user

To implement authentication, modify the existing App.tsx component to include the following:

./src/App.tsx
import { useState, useEffect } from "react"
import "./App.css"
import { FrontendApi, Configuration, Session } from "@ory/client-fetch"

interface AppProps {
msg?: string
}

const basePath = import.meta.env.VITE_ORY_URL || "http://localhost:4000"

// Initialize Ory client
const ory = new FrontendApi(
new Configuration({
basePath,
credentials: "include",
}),
)

function App({ msg }: AppProps) {
// State variables
const [session, setSession] = useState<Session | null>(null)
const [logoutUrl, setLogoutUrl] = useState<string | null>(null)

const fetchSession = async () => {
try {
// Browser automatically includes cookies in the request
const data = await ory.toSession()
setSession(data)

// Create logout URL if session exists
const logoutData = await ory.createBrowserLogoutFlow()
setLogoutUrl(logoutData.logout_url)
} catch (error) {
console.error("Error fetching session:", error)
// Redirect to login page on error
window.location.href = basePath + "/ui/login"
}
}

// Lifecycle hooks
useEffect(() => {
// Fetch the session
fetchSession()
}, [])
return (
<div className="main">
<main className="container">
{!session ? (
// Login section
<div className="login-section">
<h1 className="title">{msg}</h1>
<p>Click on "login" or "Sign Up" below to sign in.</p>
<ul className="auth-links">
<li>
<a
href={`${basePath}/ui/login`}
data-testid="sign-in"
className="auth-button"
>
Login
</a>
</li>
</ul>
</div>
) : (
// Protected content section
<div className="protected-content">
<div className="header">
<h1 className="title">{msg}</h1>
<a
href={logoutUrl || "#"}
data-testid="logout"
className="logout-button"
>
Logout
</a>
</div>
<div className="session-info">
<h2 className="subtitle">Session Information:</h2>
<pre>
<code data-testid="ory-response">
{JSON.stringify(session.identity?.traits, null, 2)}
</code>
</pre>
</div>
</div>
)}

<div className="essential-links">
<h3>Essential Links</h3>
<ul>
<li>
<a href="https://www.ory.sh">Ory Website</a>
</li>
<li>
<a href="https://github.com/ory">Ory GitHub</a>
</li>
<li>
<a href="https://www.ory.sh/docs">Documentation</a>
</li>
</ul>
</div>
</main>
</div>
)
}

export default App

This implementation checks for an authenticated session using Ory's toSession() API, which verifies the user's session cookie. Upon successful authentication, it displays the user's session information with identity traits and provides logout functionality. If authentication fails, the system automatically redirects to Ory's login UI.

Run your React app

Now that your app is ready, it's time to run it! Start the React development server:

npm run dev

Go to localhost:5173 to access your application.

Make authenticated calls to your API

To make authenticated requests to your API there are two main components:

  • When making AJAX requests you must set {"credentials": "include"} in the options. For the fetch method it looks like this:
./src/App.tsx
import { useState, useEffect } from "react"
import "./App.css"
import { FrontendApi, Configuration, Session } from "@ory/client-fetch"

interface AppProps {
msg?: string
}

const basePath = import.meta.env.VITE_ORY_URL || "http://localhost:4000"
const apiUrl = import.meta.env.API_URL || "http://localhost:8081"

// Initialize Ory client
const ory = new FrontendApi(
new Configuration({
basePath,
credentials: "include",
}),
)

function App({ msg }: AppProps) {
const [session, setSession] = useState<Session | null>(null)
const [logoutUrl, setLogoutUrl] = useState<string | null>(null)
// State variable to hold API response
const [apiResponse, setApiResponse] = useState<any | null>(null)

const fetchSession = async () => {
try {
// Browser automatically includes cookies in the request
const data = await ory.toSession()
setSession(data)
const logoutData = await ory.createBrowserLogoutFlow()
setLogoutUrl(logoutData.logout_url)
} catch (error) {
console.error("Error fetching session:", error)
// Redirect to login page on error, similar to Express middleware
window.location.href = basePath + "/ui/login"
}
}

const fetchApiHello = async () => {
try {
const res = await fetch(`${apiUrl}/api/hello`, {
// Do not forget to set this - it is required to send the session cookie!
credentials: "include",
})

if (res.ok) {
const data = await res.json()
setApiResponse(data)
}
} catch (error) {
console.error("Error fetching API response:", error)
}
}

// Lifecycle hooks
useEffect(() => {
fetchSession()
// Make an authenticated API call
fetchApiHello()
}, [])

return (
<div className="main">
<main className="container">
{!session ? (
// Login section
<div className="login-section">
<h1 className="title">{msg}</h1>
<p>Click on "login" or "Sign Up" below to sign in.</p>
<ul className="auth-links">
<li>
<a
href={`${basePath}/ui/login`}
data-testid="sign-in"
className="auth-button"
>
Login
</a>
</li>
</ul>
</div>
) : (
// Protected content section
<div className="protected-content">
<div className="header">
<h1 className="title">{msg}</h1>
<a
href={logoutUrl || "#"}
data-testid="logout"
className="logout-button"
>
Logout
</a>
</div>

<div className="api-info">
<h2 className="subtitle">API Response:</h2>
<pre>
<code data-testid="api-response">
{JSON.stringify(apiResponse, null, 2)}
</code>
</pre>
</div>
</div>
)}

<div className="essential-links">
<h3>Essential Links</h3>
<ul>
<li>
<a href="https://www.ory.sh">Ory Website</a>
</li>
<li>
<a href="https://github.com/ory">Ory GitHub</a>
</li>
<li>
<a href="https://www.ory.sh/docs">Documentation</a>
</li>
</ul>
</div>
</main>
</div>
)
}

export default App
  • Your API must have a CORS middleware with credentials: true and Access-Control-Allow-Origin of your frontend app (here http://localhost:5173).

Let's put this into action. Create a simple HTTP API with express. Run:

cd src
mkdir api
cd api
npm init --y
npm i --save @ory/client-fetch express cors
touch index.js

Next, create a simple API in index.js:

api/index.js
const express = require("express")
const cors = require("cors")
const { FrontendApi, Configuration } = require("@ory/client-fetch")

const app = express()

const ory = new FrontendApi(
new Configuration({
// Points to the local Ory API server (Ory TunneL).
basePath: process.env.ORY_URL || "http://localhost:4000",
credentials: "include",
}),
)

app.use(
cors({
origin: process.env.UI_URL || "http://localhost:5173",
credentials: true,
}),
)

app.use((req, res, next) => {
// A simple middleware to authenticate the request.
ory
.toSession({ cookie: req.header("cookie") })
.then((session) => {
req.session = session
next()
})
.catch((err) => {
console.error("Error fetching session:", err)
res.status(401).json({ error: "Unauthorized" })
})
})

app.get("/api/hello", (req, res) => {
console.log("Session:", req.session)
res.json({
message: "Hello from our API!",
identity_traits: req.session.identity,
})
})

const port = process.env.PORT || 8081
app.listen(port, () => {
try {
console.log(`Example app listening on port ${port}`)
} catch (err) {
console.error("Error starting server:", err)
}
})

Finally, start the server:

node index.js

See it in action

Access your react app at localhost:5173, sign in, and see if you can make authenticated requests to your API!

Go to production

To promote this app to production:

  1. Build the React app and run it somewhere (for example on Vercel or Netlify)
  2. Deploy the API (for example, on Heroku)
  3. Connect your project with a custom domain

These three components must be hosted on the same top-level domain as they were on your local machine:

ComponentProductionLocal
React Appwww.example.orglocalhost:5173
APIapi.example.orglocalhost:8081
Oryory.example.orglocalhost:4000