Skip to main content

Protecting Routes

This guide shows how to restrict access to routes in a Node.js server-side application based on authentication state. You will create reusable middleware that checks whether the current user has an active session before allowing access to protected resources.

Prerequisites

How It Works

The isSignedIn method checks whether the user's session exists and is still valid. If the access token has expired but a refresh token exists, the SDK automatically refreshes the token before returning the result. Use this method in a middleware function to gate access to protected routes.

Create an Authentication Middleware

src/middleware/requireAuth.ts
import { Request, Response, NextFunction } from 'express'
import { CookieConfig } from '@thunderid/node'
import auth from '../auth'

export async function requireAuth(
req: Request,
res: Response,
next: NextFunction,
): Promise<void> {
const sessionId = req.cookies[CookieConfig.SESSION_COOKIE_NAME]

if (!sessionId) {
res.redirect('/sign-in')
return
}

const signedIn = await auth.isSignedIn(sessionId)

if (!signedIn) {
res.redirect('/sign-in')
return
}

next()
}

Apply Middleware to Routes

Protect Individual Routes

Apply the middleware to specific routes that require authentication.

src/routes/dashboard.ts
import { requireAuth } from '../middleware/requireAuth'

app.get('/dashboard', requireAuth, (req, res) => {
res.render('dashboard')
})

app.get('/profile', requireAuth, async (req, res) => {
const sessionId = req.cookies[CookieConfig.SESSION_COOKIE_NAME]
const user = await auth.getUser(sessionId)
res.render('profile', { user })
})

Protect an Entire Router

Apply the middleware to all routes within a router using router.use.

src/routes/protected.ts
import { Router } from 'express'
import { requireAuth } from '../middleware/requireAuth'

const router = Router()

router.use(requireAuth)

router.get('/dashboard', (req, res) => {
res.render('dashboard')
})

router.get('/settings', (req, res) => {
res.render('settings')
})

export default router

Attach User Data to the Request

Extend the request object to carry user information, so protected route handlers can access it without fetching it again.

src/middleware/requireAuth.ts
import { Request, Response, NextFunction } from 'express'
import { CookieConfig, User } from '@thunderid/node'
import auth from '../auth'

declare global {
namespace Express {
interface Request {
user?: User
}
}
}

export async function requireAuth(
req: Request,
res: Response,
next: NextFunction,
): Promise<void> {
const sessionId = req.cookies[CookieConfig.SESSION_COOKIE_NAME]

if (!sessionId) {
res.redirect('/sign-in')
return
}

const signedIn = await auth.isSignedIn(sessionId)

if (!signedIn) {
res.redirect('/sign-in')
return
}

req.user = await auth.getUser(sessionId)

next()
}

Route handlers can then access the user directly from the request:

app.get('/dashboard', requireAuth, (req, res) => {
res.render('dashboard', { user: req.user })
})

Handle Token Refresh Failures

The isSignedIn method attempts to refresh an expired access token automatically. If the refresh fails (for example, the refresh token has also expired), it returns false. The middleware handles this by redirecting to the sign-in route.

To distinguish an expired session from a network failure, you can catch errors explicitly:

src/middleware/requireAuth.ts
export async function requireAuth(
req: Request,
res: Response,
next: NextFunction,
): Promise<void> {
const sessionId = req.cookies[CookieConfig.SESSION_COOKIE_NAME]

if (!sessionId) {
res.redirect('/sign-in')
return
}

try {
const signedIn = await auth.isSignedIn(sessionId)

if (!signedIn) {
res.clearCookie(CookieConfig.SESSION_COOKIE_NAME)
res.redirect('/sign-in')
return
}
} catch (error) {
res.status(500).send('Authentication check failed.')
return
}

next()
}

Next Steps

ThunderID LogoThunderID Logo

Product

DocsAPIsSDKs
© WSO2 LLC. All rights reserved.Privacy PolicyCookie Policy