Implementing Authentication Flows
This guide shows how to implement user registration and login flows in your application using Ory's browser-based authentication.
Registration Flow
The registration flow lets new users create accounts in your application. Here's how to implement it:
Initialize a registration flow
Redirect the user to the registration page or create a registration flow programmatically
Render the registration form
Fetch and display the registration UI with all necessary fields
Submit the form
Handle form submission and process the response
- JavaScript/Node.js
- React
- Next.js
- Go
- cURL
Create Registration Flow
const { Configuration, FrontendApi } = require("@ory/client")
// Initialize the SDK
const ory = new FrontendApi(
new Configuration({
basePath: "https://$PROJECT_SLUG.projects.oryapis.com",
baseOptions: {
withCredentials: true,
},
}),
)
// Create a new registration flow
async function createRegistrationFlow(req, res) {
try {
// Initialize the registration flow
const { data: flow } = await ory.createBrowserRegistrationFlow({
returnTo: "/welcome", // Optional return URL after successful registration
})
// Render the form or return the flow data
res.render("registration", { flow })
} catch (err) {
console.error("Error creating registration flow:", err)
res.status(500).json({ error: "Could not create registration flow" })
}
}
// Submit registration data
async function submitRegistration(req, res) {
const flowId = req.body.flow // The flow ID from the form
try {
// Submit the form data
const { data } = await ory.submitSelfServiceRegistrationFlow(flowId, {
method: "password",
password: req.body.password,
traits: {
email: req.body.email,
name: req.body.name,
},
})
// Successful registration, redirect user
res.redirect(data.return_to || "/welcome")
} catch (err) {
// Handle errors
console.error("Registration error:", err.response?.data)
// Update the flow with the error and render the form again
res.render("registration", {
flow: err.response?.data,
error: err.response?.data?.ui.messages,
})
}
}
Create Registration Component
import { Configuration, FrontendApi, RegistrationFlow } from "@ory/client"
import { useEffect, useState } from "react"
// Initialize the SDK
const ory = new FrontendApi(
new Configuration({
basePath: "https://$PROJECT_SLUG.projects.oryapis.com",
baseOptions: {
withCredentials: true,
},
}),
)
export const Registration = () => {
const [flow, setFlow] = useState<RegistrationFlow | null>(null)
useEffect(() => {
// Create a registration flow when the component mounts
ory
.createBrowserRegistrationFlow({
returnTo: "/welcome", // Optional return URL after successful registration
})
.then(({ data }) => {
setFlow(data)
})
.catch((err) => {
console.error("Could not create registration flow", err)
})
}, [])
if (!flow) {
return <div>Loading...</div>
}
return (
<div>
<h2>Register a new account</h2>
{/* Display any flow error messages */}
{flow.ui.messages?.map((message, i) => (
<div key={i} className="error">
{message.text}
</div>
))}
{/* Render the registration form */}
<form action={flow.ui.action} method={flow.ui.method}>
{/* Hidden field for the csrf_token */}
<input
type="hidden"
name="csrf_token"
value={
flow.ui.nodes.find((n) => n.attributes.name === "csrf_token")
?.attributes.value || ""
}
/>
{/* Email field */}
<div>
<label htmlFor="email">Email</label>
<input
type="email"
id="email"
name="traits.email"
defaultValue={
flow.ui.nodes.find((n) => n.attributes.name === "traits.email")
?.attributes.value || ""
}
required
/>
</div>
{/* Password field */}
<div>
<label htmlFor="password">Password</label>
<input type="password" id="password" name="password" required />
</div>
<button type="submit">Register</button>
</form>
</div>
)
}
Registration in Next.js
// In lib/ory.js
import { Configuration, FrontendApi } from "@ory/client"
import { edgeConfig } from "@ory/integrations/next"
// Initialize the SDK
export const ory = new FrontendApi(
new Configuration({
...edgeConfig,
baseOptions: {
withCredentials: true,
},
}),
)
// In pages/registration.js
import { useRouter } from "next/router"
import { useEffect, useState } from "react"
import { ory } from "../lib/ory"
export default function RegistrationPage() {
const [flow, setFlow] = useState(null)
const router = useRouter()
useEffect(() => {
// Get the flow ID from the URL if available
const { flow: flowId, return_to } = router.query
if (flowId) {
// If we have a flow ID, fetch the flow
ory
.getRegistrationFlow({ id: flowId })
.then(({ data }) => {
setFlow(data)
})
.catch((err) => {
// If the flow is expired or invalid, create a new one
if (err.response?.status === 410) {
return router.push("/registration")
}
console.error("Could not get registration flow", err)
})
} else {
// Otherwise create a new registration flow
ory
.createBrowserRegistrationFlow({
returnTo: return_to || "/welcome",
})
.then(({ data }) => {
setFlow(data)
// Update the URL to include the flow ID
router.push(`/registration?flow=${data.id}`, undefined, {
shallow: true,
})
})
.catch((err) => {
console.error("Could not create registration flow", err)
})
}
}, [router.query.flow])
if (!flow) {
return <div>Loading...</div>
}
return (
<div>
<h2>Register a new account</h2>
{/* Display any flow error messages */}
{flow.ui.messages?.map((message, i) => (
<div key={i} className="error">
{message.text}
</div>
))}
{/* Render the registration form */}
<form action={flow.ui.action} method={flow.ui.method}>
{/* Form fields similar to React example */}
{/* ... */}
</form>
</div>
)
}
Create Registration Flow in Go
package main
import (
"context"
"fmt"
"net/http"
ory "github.com/ory/client-go"
)
func createRegistrationHandler(w http.ResponseWriter, r *http.Request) {
// Initialize the Ory client
configuration := ory.NewConfiguration()
configuration.Servers = []ory.ServerConfiguration{
{
URL: "https://$PROJECT_SLUG.projects.oryapis.com",
},
}
client := ory.NewAPIClient(configuration)
// Create a registration flow
flow, resp, err := client.FrontendApi.CreateBrowserRegistrationFlow(context.Background()).
ReturnTo("/welcome").Execute()
if err != nil {
fmt.Fprintf(w, "Error creating registration flow: %v\n", err)
return
}
// Redirect user to the self-service registration UI
http.Redirect(w, r, flow.Ui.Action, http.StatusFound)
}
Create and Submit Registration with cURL
# Create a registration flow
curl -X GET \
'https://$PROJECT_SLUG.projects.oryapis.com/self-service/registration/browser' \
-H 'Accept: application/json' \
--verbose
# The response contains a flow ID that you'll need for the next request
# Submit registration data
curl -X POST \
'https://$PROJECT_SLUG.projects.oryapis.com/self-service/registration?flow=FLOW_ID' \
-H 'Content-Type: application/json' \
-d '{
"method": "password",
"password": "secure-password",
"traits": {
"email": "user@example.com",
"name": "Test User"
},
"csrf_token": "THE_CSRF_TOKEN_FROM_FLOW"
}'
Login Flow
The login flow authenticates existing users. Here's how to implement it:
Initialize a login flow
Redirect the user to the login page or create a login flow programmatically
Render the login form
Fetch and display the login UI with fields for credentials
Submit the form
Handle form submission and process the response
- JavaScript/Node.js
- React
- Next.js
- Go
- cURL
Create Login Flow
const { Configuration, FrontendApi } = require("@ory/client")
// Initialize the SDK
const ory = new FrontendApi(
new Configuration({
basePath: "https://$PROJECT_SLUG.projects.oryapis.com",
baseOptions: {
withCredentials: true,
},
}),
)
// Create a new login flow
async function createLoginFlow(req, res) {
try {
// Initialize the login flow
const { data: flow } = await ory.createBrowserLoginFlow({
returnTo: "/dashboard", // Optional return URL after successful login
refresh: false, // Set to true to refresh an existing login session
})
// Render the form or return the flow data
res.render("login", { flow })
} catch (err) {
console.error("Error creating login flow:", err)
res.status(500).json({ error: "Could not create login flow" })
}
}
// Submit login data
async function submitLogin(req, res) {
const flowId = req.body.flow // The flow ID from the form
try {
// Submit the form data
const { data } = await ory.submitSelfServiceLoginFlow(flowId, {
method: "password",
identifier: req.body.identifier, // Username or email
password: req.body.password,
})
// Successful login, redirect user
res.redirect(data.return_to || "/dashboard")
} catch (err) {
// Handle errors
console.error("Login error:", err.response?.data)
// Update the flow with the error and render the form again
res.render("login", {
flow: err.response?.data,
error: err.response?.data?.ui.messages,
})
}
}
Create Login Component
import { Configuration, FrontendApi, LoginFlow } from "@ory/client"
import { useEffect, useState } from "react"
// Initialize the SDK
const ory = new FrontendApi(
new Configuration({
basePath: "https://$PROJECT_SLUG.projects.oryapis.com",
baseOptions: {
withCredentials: true,
},
}),
)
export const Login = () => {
const [flow, setFlow] = useState<LoginFlow | null>(null)
useEffect(() => {
// Create a login flow when the component mounts
ory
.createBrowserLoginFlow({
returnTo: "/dashboard", // Optional return URL after successful login
})
.then(({ data }) => {
setFlow(data)
})
.catch((err) => {
console.error("Could not create login flow", err)
})
}, [])
if (!flow) {
return <div>Loading...</div>
}
return (
<div>
<h2>Sign in to your account</h2>
{/* Display any flow error messages */}
{flow.ui.messages?.map((message, i) => (
<div key={i} className="error">
{message.text}
</div>
))}
{/* Render the login form */}
<form action={flow.ui.action} method={flow.ui.method}>
{/* Hidden field for the csrf_token */}
<input
type="hidden"
name="csrf_token"
value={
flow.ui.nodes.find((n) => n.attributes.name === "csrf_token")
?.attributes.value || ""
}
/>
{/* Identifier field */}
<div>
<label htmlFor="identifier">Email or Username</label>
<input
type="text"
id="identifier"
name="identifier"
defaultValue={
flow.ui.nodes.find((n) => n.attributes.name === "identifier")
?.attributes.value || ""
}
required
/>
</div>
{/* Password field */}
<div>
<label htmlFor="password">Password</label>
<input type="password" id="password" name="password" required />
</div>
<button type="submit">Sign In</button>
</form>
</div>
)
}
Login Page in Next.js
// In pages/login.js
import { useRouter } from "next/router"
import { useEffect, useState } from "react"
import { ory } from "../lib/ory"
export default function LoginPage() {
const [flow, setFlow] = useState(null)
const router = useRouter()
useEffect(() => {
// Get the flow ID from the URL if available
const { flow: flowId, return_to } = router.query
if (flowId) {
// If we have a flow ID, fetch the flow
ory
.getLoginFlow({ id: flowId })
.then(({ data }) => {
setFlow(data)
})
.catch((err) => {
// If the flow is expired or invalid, create a new one
if (err.response?.status === 410) {
return router.push("/login")
}
console.error("Could not get login flow", err)
})
} else {
// Otherwise create a new login flow
ory
.createBrowserLoginFlow({
returnTo: return_to || "/dashboard",
refresh: false, // Set to true to refresh an existing login session
})
.then(({ data }) => {
setFlow(data)
// Update the URL to include the flow ID
router.push(`/login?flow=${data.id}`, undefined, { shallow: true })
})
.catch((err) => {
console.error("Could not create login flow", err)
})
}
}, [router.query.flow])
if (!flow) {
return <div>Loading...</div>
}
return (
<div>
<h2>Sign in to your account</h2>
{/* Display any flow error messages */}
{flow.ui.messages?.map((message, i) => (
<div key={i} className="error">
{message.text}
</div>
))}
{/* Render the login form */}
<form action={flow.ui.action} method={flow.ui.method}>
{/* Form fields similar to React example */}
{/* ... */}
</form>
</div>
)
}
Create Login Flow in Go
package main
import (
"context"
"fmt"
"net/http"
ory "github.com/ory/client-go"
)
func createLoginHandler(w http.ResponseWriter, r *http.Request) {
// Initialize the Ory client
configuration := ory.NewConfiguration()
configuration.Servers = []ory.ServerConfiguration{
{
URL: "https://$PROJECT_SLUG.projects.oryapis.com",
},
}
client := ory.NewAPIClient(configuration)
// Create a login flow
flow, resp, err := client.FrontendApi.CreateBrowserLoginFlow(context.Background()).
ReturnTo("/dashboard").
Refresh(false).Execute()
if err != nil {
fmt.Fprintf(w, "Error creating login flow: %v\n", err)
return
}
// Redirect user to the self-service login UI
http.Redirect(w, r, flow.Ui.Action, http.StatusFound)
}
Create and Submit Login with cURL
# Create a login flow
curl -X GET \
'https://$PROJECT_SLUG.projects.oryapis.com/self-service/login/browser' \
-H 'Accept: application/json' \
--verbose
# The response contains a flow ID that you'll need for the next request
# Submit login data
curl -X POST \
'https://$PROJECT_SLUG.projects.oryapis.com/self-service/login?flow=FLOW_ID' \
-H 'Content-Type: application/json' \
-d '{
"method": "password",
"identifier": "user@example.com",
"password": "secure-password",
"csrf_token": "THE_CSRF_TOKEN_FROM_FLOW"
}'
Session Management
After a successful login, Ory sets a session cookie. You can check if a user is authenticated and access their information:
- JavaScript/Node.js
- React
- Next.js
- Go
- cURL
// Check if a user is authenticated
const requireAuth = async (req, res, next) => {
try {
// This verifies the session and throws an error if not authenticated
const { data: session } = await ory.toSession({
cookie: req.header("cookie"),
})
// Make session available to the route handler
req.session = session
next()
} catch (err) {
// Not authenticated, redirect to login
res.redirect("/login")
}
}
// Use the middleware for protected routes
app.get("/dashboard", requireAuth, (req, res) => {
// Access user data
const userId = req.session.identity.id
const email = req.session.identity.traits.email
res.render("dashboard", { user: req.session.identity })
})
import { useEffect, useState } from "react"
import { ory } from "../lib/ory"
export const Dashboard = () => {
const [session, setSession] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
// Check if the user is authenticated
ory
.toSession()
.then(({ data }) => {
setSession(data)
setLoading(false)
})
.catch(() => {
// Not authenticated, redirect to login
window.location.href = "/login?return_to=/dashboard"
})
}, [])
if (loading) {
return <div>Loading...</div>
}
return (
<div>
<h2>
Welcome, {session.identity.traits.name || session.identity.traits.email}
</h2>
<div>
<p>User ID: {session.identity.id}</p>
<p>Email: {session.identity.traits.email}</p>
</div>
<button onClick={() => (window.location.href = "/logout")}>
Log Out
</button>
</div>
)
}
package main
import (
"context"
"fmt"
"net/http"
ory "github.com/ory/client-go"
)
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Initialize the Ory client
configuration := ory.NewConfiguration()
configuration.Servers = []ory.ServerConfiguration{
{
URL: "https://$PROJECT_SLUG.projects.oryapis.com",
},
}
client := ory.NewAPIClient(configuration)
// Check if user is authenticated
cookie := r.Header.Get("Cookie")
session, _, err := client.FrontendApi.ToSession(context.Background()).Cookie(cookie).Execute()
if err != nil {
// Not authenticated, redirect to login
http.Redirect(w, r, "/login", http.StatusFound)
return
}
// User is authenticated, add session to context
ctx := context.WithValue(r.Context(), "session", session)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func dashboardHandler(w http.ResponseWriter, r *http.Request) {
// Get session from context
session := r.Context().Value("session").(*ory.Session)
// Access user data
userId := session.Identity.Id
email := session.Identity.Traits["email"]
// Render dashboard with user data
fmt.Fprintf(w, "Welcome, %s", email)
}
# Check if a user is authenticated
curl -X GET \
'https://$PROJECT_SLUG.projects.oryapis.com/sessions/whoami' \
-H 'Accept: application/json' \
-H 'Cookie: ory_session_YOUR_PROJECT=YOUR_SESSION_COOKIE' \
--verbose
Logout Flow
To log users out, use the logout flow:
- JavaScript/Node.js
- React
- Next.js
- Go
- cURL
// Create logout route
app.get("/logout", async (req, res) => {
try {
// Create a logout flow
const { data: logoutFlow } = await ory.createBrowserLogoutFlow({
cookie: req.header("cookie"),
})
// Redirect to logout URL
res.redirect(logoutFlow.logout_url)
} catch (err) {
console.error("Logout error:", err)
// Already logged out or error, redirect to login
res.redirect("/login")
}
})
import { useEffect } from "react"
import { ory } from "../lib/ory"
export const Logout = () => {
useEffect(() => {
// Create a logout flow and redirect to it
ory
.createBrowserLogoutFlow()
.then(({ data }) => {
// Redirect to the logout URL which will destroy the session
window.location.href = data.logout_url
})
.catch((err) => {
console.error("Logout error:", err)
// Already logged out or error, redirect to login
window.location.href = "/login"
})
}, [])
return <div>Logging out...</div>
}
package main
import (
"context"
"net/http"
ory "github.com/ory/client-go"
)
func logoutHandler(w http.ResponseWriter, r *http.Request) {
// Initialize the Ory client
configuration := ory.NewConfiguration()
configuration.Servers = []ory.ServerConfiguration{
{
URL: "https://$PROJECT_SLUG.projects.oryapis.com",
},
}
client := ory.NewAPIClient(configuration)
// Create a logout flow
cookie := r.Header.Get("Cookie")
flow, _, err := client.FrontendApi.CreateBrowserLogoutFlow(context.Background()).Cookie(cookie).Execute()
if err != nil {
// Already logged out or error, redirect to login
http.Redirect(w, r, "/login", http.StatusFound)
return
}
// Redirect to logout URL
http.Redirect(w, r, flow.LogoutUrl, http.StatusFound)
}
# Create a logout flow
curl -X GET \
'https://$PROJECT_SLUG.projects.oryapis.com/self-service/logout/browser' \
-H 'Accept: application/json' \
-H 'Cookie: ory_session_YOUR_PROJECT=YOUR_SESSION_COOKIE' \
--verbose
# The response contains a logout_url that you'll need to redirect the user to
Error Handling
Handle authentication errors gracefully by checking for specific error types and displaying appropriate messages:
- JavaScript/Node.js
- React
- Next.js
- Go
- cURL
async function login(req, res) {
try {
// ... login code ...
} catch (err) {
if (err.response?.status === 400) {
// Validation errors (wrong password, etc.)
const errorMessages = err.response.data.ui.messages || []
res.render("login", {
flow: err.response.data,
error: errorMessages.map((msg) => msg.text).join(", "),
})
} else if (err.response?.status === 410) {
// Flow expired
res.redirect("/login") // Create a new flow
} else if (err.response?.status === 401) {
// Unauthorized
res.redirect("/login?error=invalid_credentials")
} else {
// Server error
console.error("Unexpected error:", err)
res
.status(500)
.render("error", { message: "An unexpected error occurred" })
}
}
}
const handleSubmitLogin = async (flowId, formData) => {
try {
// Submit the login data
const { data } = await ory.submitSelfServiceLoginFlow(flowId, formData)
// Success - redirect to return_to or dashboard
window.location.href = data.return_to || "/dashboard"
} catch (err) {
if (err.response?.status === 400) {
// Validation errors (wrong password, etc.)
// Update the flow with the error data
setFlow(err.response.data)
setError(err.response.data.ui.messages?.map((msg) => msg.text).join(", "))
} else if (err.response?.status === 410) {
// Flow expired - create a new one
window.location.href = "/login"
} else if (err.response?.status === 401) {
// Unauthorized
setError("Invalid username or password")
} else {
// Server error
console.error("Unexpected error:", err)
setError("An unexpected error occurred")
}
}
}
Next Steps
Now that you have implemented basic authentication flows in your application, you can:
- Customize the user interface
- Add multi-factor authentication
- Configure session lifespans
- Implement account recovery
In the next section, we'll cover session management in more detail, including checking session status, refreshing sessions, and handling logouts.