Elevate Your Next.js Security: Middleware Authorization with Userfront

In this guide, I will show you how to secure your Next.js application with Userfront by implementing middleware-level authorization and leaving component authorization practices.

Implementing robust security measures like IAM is essential for protecting user data and maintaining compliance. This builds trust with your users and safeguards your application from potential vulnerabilities.

Many developers overlook comprehensive IAM solutions, opting for basic authentication methods that can expose their applications to significant security risks.

Key Takeaways

  • Explore Userfront’s features like SSO, multi-tenancy, two-factor authentication, and custom JWTs—all available with a generous free tier.

  • Understand the drawbacks of component-level user authorization.

  • Ensure middleware-level JWT verification for robust authorization.


As applications grow in complexity, robust security becomes increasingly crucial. Userfront provides a modern IAM solution that integrates seamlessly with Next.js. This guide walks you through integrating Userfront authentication, emphasizing best practices for a secure setup.

Understanding Userfront in the IAM Landscape

Userfront simplifies the authentication process for developers with its easy-to-use SDK. Key features include:

  • Single Sign-On (SSO): Users can log in once and access multiple applications without re-authenticating.

  • Multi-Tenancy: Supports multiple clients with a single Userfront account, ideal for SaaS applications.

  • Two-Factor Authentication (2FA): Adds an extra layer of security by requiring a second form of verification.

  • SOC 2 Compliance: Adheres to strict data privacy and security standards.

Learn more about Userfront here.

Getting Started with Userfront

The Project is already settled up with Userfront. Clone the repository from this link. To learn about the fundamentals of setting up Userfront with Next.js, refer to the official documentation https://userfront.com/docs/examples/next

Setting Up Userfront in Your Next.js Application

Environment Variables

Create an .env file in your project root:

# WorkspaceId can be found at https://userfront.com/dashboard
NEXT_PUBLIC_USERFRONT_WORKSPACE_ID="..."

# Public JWT key can be found at https://userfront.com/test/dashboard/jwt?tab=public
# Make sure to get Base64 encoded public key
JWT_PUBLIC_KEY_BASE64="..."

Project Structure

app
├── (auth)
│   ├── layout.tsx
│   ├── login
│   │   └── page.tsx
│   ├── reset
│   │   └── page.tsx
│   └── signup
│       └── page.tsx
├── (public)
│   └── home
│       └── page.tsx
├── (secure)
│   └── dashboard
│       └── page.tsx
├── _components
│   └── Header.tsx
├── globals.css
└── layout.tsx

Directory Breakdown

  • (auth): Contains authentication-related pages (login, signup, reset).

  • (public): Public-facing pages (like home) accessible without authentication.

  • (secure): Pages requiring authentication (like the dashboard).

Why Component-Level Authorization is Problematic

Consider this approach:

// app/(secure)/layout.tsx
"use client";

import * as React from "react";
import { useRouter } from "next/navigation";
import { useUserfront } from "@userfront/next/client";

export default function SecureLayout({ children }: { children: React.ReactNode }) {
  const router = useRouter();
  const { isAuthenticated, isLoading } = useUserfront();

  React.useEffect(() => {
    if (isAuthenticated || isLoading || !router) return;
    router.push("/login");
  }, [isAuthenticated, isLoading, router]);

  if (!isAuthenticated || isLoading) {
    return null;
  }

  return children;
}

added a console log to see how it’s going into the secure component everytime and redirects in split second:

Drawbacks

  1. Redirects: Unnecessary redirects and allowing component access for a split second leaving security flaws and bad UX.

  2. Insecure Logic: Client-side checks can be manipulated, exposing sensitive data.

  3. Race Conditions: Delays in loading may expose restricted content.

  4. Performance Issues: Unnecessary rendering can slow down your application.

  5. Complexity: Mixing authentication logic with UI rendering complicates maintenance.

  6. Scalability: Harder to manage as the application grows.

Implementing Middleware for Authentication

Create a middleware.ts file for JWT verification:

// middleware.ts
"use server";

import { NextRequest, NextResponse } from "next/server";
import { jwtVerify, importSPKI, JWTPayload } from "jose";

const JWT_PUBLIC_KEY_BASE64 = process.env.JWT_PUBLIC_KEY_BASE64!;
const WORKSPACE_ID = process.env.NEXT_PUBLIC_USERFRONT_WORKSPACE_ID

interface UserFrontJwtPayload extends JWTPayload {
  userId?: string;
}

async function verifyToken(token: string, publicKeyBase64: string) {
  try {
    const publicKey = await importSPKI(
      Buffer.from(publicKeyBase64, "base64").toString("utf-8"),
      "RS256"
    );
    const { payload } = await jwtVerify(token, publicKey, {
      algorithms: ["RS256"],
    });
    return payload as UserFrontJwtPayload;
  } catch (error) {
    console.log("JWT verification failed:", error);
    return null;
  }
}

const pathsToExclude = /^(?!\/(api|_next\/static|favicon\.ico|manifest|icon|static|mergn)).*$/;
const publicPagesSet = new Set<string>(["/home"]);
const privatePagesSet = new Set<string>(["/dashboard"]);
const rootRegex = /^\/($|\?.+|#.+)?$/;

export default async function middleware(req: NextRequest) {
  if (!pathsToExclude.test(req.nextUrl.pathname) || publicPagesSet.has(req.nextUrl.pathname)) {
    return NextResponse.next();
  }

  const accessToken = req.cookies.get(`access.${WORKSPACE_ID}`)?.value;
  const decoded = accessToken ? await verifyToken(accessToken, JWT_PUBLIC_KEY_BASE64) : null;
  const isAuthenticated = decoded && decoded.userId;

  if (rootRegex.test(req.nextUrl.pathname)) {
    return isAuthenticated ? NextResponse.redirect(new URL("/dashboard", req.url)) : NextResponse.redirect(new URL("/login", req.url));
  }

  if (privatePagesSet.has(req.nextUrl.pathname) && !isAuthenticated) {
    return NextResponse.redirect(new URL("/login", req.url));
  }

  if (req.nextUrl.pathname.startsWith("/login") && isAuthenticated) {
    return NextResponse.redirect(new URL("/dashboard", req.url));
  }
}

see the instant redirect in action:

Middleware Explained

Middleware processes requests before they reach routes, centralizing authentication logic. Key functions include:

  • JWT Verification: Ensures only authenticated users can access protected routes.

  • Path Exclusions: Optimizes performance by excluding public routes from authentication checks.

  • Redirect Logic: Manages user redirection based on authentication status.

Conclusion

By following these steps, you can effectively secure your Next.js application with Userfront's IAM capabilities. Middleware enhances security, ensuring only authorized users access sensitive routes while providing a seamless user experience.

For more details on the Userfront and its features, visit the Userfront documentation. With Userfront’s generous free tier, you can explore advanced features like SSO, multi-tenancy, and more without any initial investment.

That’s it! I hope you found this guide helpful. 🚀

Feel free to follow me on GitHub and LinkedIn for more guides.

GitHub

LinkedIn


Stay awesome and happy coding! ✨

0
Subscribe to my newsletter

Read articles from Syed Muhammad Yaseen directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Syed Muhammad Yaseen
Syed Muhammad Yaseen

Hi there 👋 This is SMY Curious to know my standout highlights? Let me share some with you: 🎯 Professional Experience: ❯ Full Stack Engineer with 3+ years of expertise in the JavaScript ecosystem (Backend / Frontend / AWS). Consistently delivers high-quality SaaS applications by developing, testing, documenting, and maintaining while ensuring code quality and reliability through unit testing. ❯ Regularly learning and upskilling myself on various technical and soft skills by participating in initiatives, courses, and developing POCs. 🎯 Academic Experience: ❯ Pursued a Bachelor of Science in Computer Science with a 3.72 CGPA and a four-time Merit Scholarship holder. ❯ My academic accomplishments have been further recognized with awards, and I have actively contributed as a volunteer, expanding my horizons beyond traditional educational boundaries. 🎯 Publications: ❯ Passionate about leveraging technology to drive positive change, I am an avid writer and community contributor. I love sharing my insights, experiences, and knowledge through thought-provoking articles and engaging discussions. ❯ Discover a collection of my insightful articles on various topics by visiting my profile on hashnode 🌐 https://smy.hashnode.dev/ 🎯 Interests: ❯ At 4 years old, I was fascinated by computers, starting with gaming on DOS and exploring Pentium III, solidifying my fascination and paving the way for a lifelong journey. ❯ Fast forward to today, and I find myself immersed in the ever-evolving landscape of technology. I'm passionate about leveraging technology to solve real-world challenges and make a positive difference in our interconnected world. 👋 If you're passionate about technology too, or if you're eager to explore the vast opportunities it presents, let's connect 🤝 LinkedIn: https://www.linkedin.com/in/sm-y/