Skip to main content

Complete Authentication Example

This guide provides a complete working example that integrates all the authentication flows we've covered in this Day 1 Essentials series. You can use this as a starting point for your own applications.

Complete Example Application

Below is a complete implementation that includes:

  1. Registration
  2. Login
  3. Session management
  4. Social sign-in
  5. Logout
  6. Protected routes

Complete Express.js Example

const express = require("express")
const { Configuration, FrontendApi } = require("@ory/client")
const app = express()

// Initialize Ory SDK
const ory = new FrontendApi(
new Configuration({
basePath: process.env.ORY_URL || "http://localhost:4000",
baseOptions: {
withCredentials: true,
},
}),
)

// Middleware
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
app.set("view engine", "ejs")

// Authentication middleware
const requireAuth = async (req, res, next) => {
try {
const { data: session } = await ory.toSession({
cookie: req.header("cookie"),
})

req.session = session
next()
} catch (err) {
res.redirect("/login?return_to=" + encodeURIComponent(req.originalUrl))
}
}

// Public routes
app.get("/", (req, res) => {
res.render("home")
})

// Registration flow
app.get("/registration", async (req, res) => {
try {
const { data: flow } = await ory.createBrowserRegistrationFlow({
returnTo: req.query.return_to,
})

res.render("registration", { flow })
} catch (err) {
res.status(500).render("error", { message: "Could not initialize registration flow" })
}
})

app.post("/registration", async (req, res) => {
try {
const { data: completion } = await ory.updateRegistrationFlow({
flow: req.query.flow,
updateRegistrationFlowBody: {
method: "password",
password: req.body.password,
traits: {
email: req.body.email,
name: req.body.name,
},
csrf_token: req.body.csrf_token,
},
})

// Registration successful, redirect
res.redirect(completion.redirect_to || "/dashboard")
} catch (err) {
// Handle errors
const { data: flow } = await ory.getRegistrationFlow({
id: req.query.flow,
})

res.status(400).render("registration", { flow, error: err.response?.data?.error })
}
})

// Login flow
app.get("/login", async (req, res) => {
try {
const { data: flow } = await ory.createBrowserLoginFlow({
returnTo: req.query.return_to,
})

// Extract social providers
const socialProviders = flow.ui.nodes
.filter((node) => node.group === "oidc")
.map((node) => ({
name: node.attributes.value,
label: node.meta.label?.text || node.attributes.value,
url: `${flow.ui.action}?flow=${flow.id}&method=oidc&provider=${node.attributes.value}`,
}))

res.render("login", { flow, socialProviders })
} catch (err) {
res.status(500).render("error", { message: "Could not initialize login flow" })
}
})

app.post("/login", async (req, res) => {
try {
const { data: completion } = await ory.updateLoginFlow({
flow: req.query.flow,
updateLoginFlowBody: {
method: "password",
identifier: req.body.identifier,
password: req.body.password,
csrf_token: req.body.csrf_token,
},
})

// Login successful, redirect
res.redirect(completion.redirect_to || "/dashboard")
} catch (err) {
// Handle errors
const { data: flow } = await ory.getLoginFlow({
id: req.query.flow,
})

res.status(400).render("login", { flow, error: err.response?.data?.error })
}
})

// OAuth callback - you don't need a special route for this
// Ory handles the callback and redirects to your return_to URL

// Logout
app.get("/logout", async (req, res) => {
try {
const { data: logoutFlow } = await ory.createBrowserLogoutFlow({
cookie: req.header("cookie"),
})

res.redirect(logoutFlow.logout_url)
} catch (err) {
// User is not logged in
res.redirect("/")
}
})

// Protected routes
app.get("/dashboard", requireAuth, (req, res) => {
res.render("dashboard", { user: req.session.identity })
})

app.get("/profile", requireAuth, (req, res) => {
// Access provider-specific data
const providerData = req.session.identity.metadata_public?.providers || {}

res.render("profile", {
user: req.session.identity,
providerData,
})
})

app.get("/settings", requireAuth, (req, res) => {
res.render("settings", { user: req.session.identity })
})

// Session refresh
app.get("/refresh-session", requireAuth, async (req, res) => {
try {
const { data: refreshedSession } = await ory.extendSession({
cookie: req.header("cookie"),
})

res.redirect(req.query.return_to || "/dashboard")
} catch (err) {
res.redirect("/login")
}
})

// Start server
const PORT = process.env.PORT || 3000
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`)
})

EJS Templates (examples):

login.ejs

<!doctype html>
<html>
<head>
<title>Login</title>
<style>
.social-providers {
margin-top: 20px;
}
.social-button {
display: block;
margin-bottom: 10px;
padding: 10px;
background: #f0f0f0;
text-decoration: none;
color: #333;
}
</style>
</head>
<body>
<h1>Login</h1>

<% if (error) { %>
<div style="color: red;"><%= error.message %></div>
<% } %>

<form action="<%= flow.ui.action %>" method="POST">
<div>
<label for="identifier">Email:</label>
<input type="text" id="identifier" name="identifier" required />
</div>
<div>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required />
</div>

<input
type="hidden"
name="csrf_token"
value="<%= flow.ui.nodes.find(n => n.attributes.name === 'csrf_token')?.attributes.value %>"
/>
<button type="submit">Login</button>
</form>

<div>
<a href="/registration">Don't have an account? Register</a>
</div>

<% if (socialProviders && socialProviders.length > 0) { %>
<div class="social-providers">
<h3>Or sign in with:</h3>
<% socialProviders.forEach(provider => { %>
<a href="<%= provider.url %>" class="social-button"> <%= provider.label %> </a>
<% }); %>
</div>
<% } %>
</body>
</html>

dashboard.ejs

<!doctype html>
<html>
<head>
<title>Dashboard</title>
</head>
<body>
<h1>Welcome to Your Dashboard</h1>
<p>Hello, <%= user.traits.name || user.traits.email %></p>

<nav>
<ul>
<li><a href="/profile">Your Profile</a></li>
<li><a href="/settings">Settings</a></li>
<li><a href="/logout">Logout</a></li>
</ul>
</nav>

<div>
<h2>Dashboard Content</h2>
<p>This is your protected dashboard. Only authenticated users can see this.</p>
</div>
</body>
</html>

profile.ejs

<!doctype html>
<html>
<head>
<title>Profile</title>
</head>
<body>
<h1>Your Profile</h1>

<div>
<h2>Profile Information</h2>
<p>Email: <%= user.traits.email %></p>
<% if (user.traits.name) { %>
<p>Name: <%= user.traits.name %></p>
<% } %>
<p>User ID: <%= user.id %></p>
</div>

<% if (Object.keys(providerData).length > 0) { %>
<div>
<h2>Connected Accounts</h2>
<% Object.entries(providerData).forEach(([provider, data]) => { %>
<div>
<h3><%= provider %></h3>
<pre><%= JSON.stringify(data, null, 2) %></pre>
</div>
<% }); %>
</div>
<% } %>

<a href="/dashboard">Back to Dashboard</a>
</body>
</html>

Here's a recommended directory structure for your authentication-enabled application:

my-auth-app/
├── node_modules/
├── public/
│ └── styles.css
├── views/
│ ├── dashboard.ejs
│ ├── error.ejs
│ ├── home.ejs
│ ├── login.ejs
│ ├── profile.ejs
│ └── registration.ejs
├── middleware/
│ └── auth.js
├── routes/
│ ├── auth.js
│ └── dashboard.js
├── app.js
├── package.json
└── .env