Skip to main content

Implementing the authentication and middleware routes

Add login route

This route generates PKCE code verifiers and state tokens for secure authentication, then redirects the user to Ory's authorization server.

index.js
// Login route
app.get("/login", async (req, res) => {
if (!config) {
return res
.status(500)
.send("Configuration not ready yet. Please try again in a moment.")
}

try {
// Generate PKCE code verifier
const code_verifier = client.randomPKCECodeVerifier()

// Calculate code challenge from verifier
const code_challenge =
await client.calculatePKCECodeChallenge(code_verifier)

// Generate state for CSRF protection
const state = client.randomState()

// Store in session for later verification
req.session.codeVerifier = code_verifier
req.session.state = state

// Build authorization parameters
const parameters = {
redirect_uri: "http://localhost:3000/callback",
scope: "openid email offline_access",
code_challenge,
code_challenge_method: "S256",
state,
}

// Build authorization URL
const redirectTo = client.buildAuthorizationUrl(config, parameters)

// Redirect user to authorization server
console.log("Redirecting to:", redirectTo.href)
res.redirect(redirectTo.href)
} catch (error) {
console.error("Login error:", error)
res.status(500).send(`Login error: ${error.message}`)
}
})

Add logout route

This route destroys the user's session and redirects them back to the home page. It cleans up all authentication tokens stored in the session.

index.js
// Logout route
app.get("/logout", (req, res) => {
// Clear session
req.session.destroy((err) => {
if (err) {
console.error("Error destroying session:", err)
}
res.redirect("/")
})
})

Create middleware to check if the user is authenticated

This middleware checks if the user has a valid access token and redirects to login if not. It also handles token expiration by attempting to refresh expired tokens before redirecting.

index.js
const requireAuth = (req, res, next) => {
if (!req.session.tokens || !req.session.tokens.access_token) {
// User is not authenticated, redirect to login
return res.redirect("/login")
}

// Check if the token is expired
if (req.session.tokens.expiresIn && req.session.tokens.expiresIn() <= 0) {
// Token is expired, we need to refresh it if we have a refresh token
if (req.session.tokens.refresh_token) {
// We'll handle refresh in a separate middleware
return refreshToken(req, res, next)
} else {
// No refresh token, redirect to login
return res.redirect("/login")
}
}

// User is authenticated with a valid token
next()
}

Add refresh token middleware

This middleware automatically renews expired access credentials by using the long-lived refresh token to obtain a new set of tokens. If successful, it updates your session with the fresh credentials; if it fails (e.g., refresh token expired), it logs you out and redirects to the login page.

index.js
// Middleware to refresh tokens
const refreshToken = async (req, res, next) => {
if (!config) {
return res
.status(500)
.send("Configuration not ready yet. Please try again in a moment.")
}

try {
// Use the refresh token to get new tokens
const tokens = await client.refreshTokenGrant(
config,
req.session.tokens.refresh_token,
)

// Update the tokens in the session
req.session.tokens = tokens

// Continue with the request
next()
} catch (error) {
console.error("Token refresh error:", error)
// Clear session and redirect to login
req.session.destroy()
res.redirect("/login")
}
}

Protect routes with middleware

This code shows how to create a protected route that requires authentication using the middleware. It fetches and displays user profile information from the userinfo endpoint using the access token.

index.js
app.get("/profile", requireAuth, async (req, res) => {
try {
// Get user info
const userInfo = await client.fetchUserInfo(
config,
req.session.tokens.access_token,
client.skipSubjectCheck,
)

res.json({
message: "This is protected data from the resource server",
email: userInfo.email,
sub: userInfo.sub,
})
} catch (error) {
console.error("Profile error:", error)
res.status(500).json({ error: `Error accessing profile: ${error.message}` })
}
})

How to check for session in an unprotected route

This code demonstrates how to handle a public route that adapts its behavior based on whether the user is authenticated.

index.js
// Home route
app.get("/", async (req, res) => {
if (req.session.tokens) {
// If we have tokens, try to get user info
let userInfo = null
try {
if (req.session.tokens.access_token) {
// For userInfo we need expectedSubject or skipSubjectCheck
userInfo = await client.fetchUserInfo(
config,
req.session.tokens.access_token,
client.skipSubjectCheck,
)
}
} catch (error) {
console.error("Error fetching user info:", error)
}

res.send(`
<html lang='en'>
<body>
<h1>Authenticated!</h1>
<h2>Tokens:</h2>
<pre>${JSON.stringify(req.session.tokens, null, 2)}</pre>
${userInfo ? `<h2>User Info:</h2><pre>${JSON.stringify(userInfo, null, 2)}</pre>` : ""}
<p><a href="/profile">View Profile API</a></p>
<p><a href="/logout">Logout</a></p>
</body>
</html>
`)
} else {
res.send(`
<html lang='en'>
<body>
<h1>Welcome</h1>
<a href="/login">Login</a>
</body>
</html>
`)
}
})