Back to Tutorials

Building a Secure Auth Flow with Auth.js

April 5, 2026
1 min read
Explore Your Brain Editorial Team

Explore Your Brain Editorial Team

Science Communication

Science Communication Certified
Peer-Reviewed by Domain Experts

Implementing authentication from scratch is one of the most perilous engineering tasks attempting by developers. Handling password hashing algorithms (bcrypt/argon2), managing short-lived access tokens, ensuring secure Cross-Site Request Forgery (CSRF) protections, and keeping up with evolving OAuth 2.0 specs are massive security surfaces. It takes a dedicated engineering team to do it perfectly.

Enter Auth.js (formerly NextAuth.js). It is an open-source library that entirely abstracts the mechanics of secure authentication workflows. By utilizing Auth.js, we can deploy a bulletproof, production-ready login system in our Next.js application in minutes.

1. Generating the Security Core

First, we install the core library. Since the transition from NextAuth to Auth.js, you'll want to use the latest packages compatible with Next.js App Router.

        npm install next-auth@beta

# Generate a strong cryptographic secret for signing JSON Web Tokens
npx auth secret
      

That command automatically creates an AUTH_SECRET environment variable in your .env file. Do not lose this key; if it rolls, all currently logged-in users will be abruptly signed out because their session cookies will no longer violently decrypt.

2. Architecting the Auth Config

Create an auth.ts file in the root of your application (or inside your src/ directory). This is your single source of truth. We will configure GitHub and Google as our Identity Providers.

        import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
import Google from "next-auth/providers/google"

// You can easily swap in a database adapter like Prisma here 
// using 'adapter: PrismaAdapter(prisma)'

export const { handlers, signIn, signOut, auth } = NextAuth({
  providers: [
    GitHub({
      clientId: process.env.GITHUB_CLIENT_ID,
      clientSecret: process.env.GITHUB_CLIENT_SECRET,
    }),
    Google({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    })
  ],
  pages: {
    signIn: '/login', // Optional: Use a custom login page instead of the default
  },
  callbacks: {
    // This callback prevents users without approved emails from logging in
    async signIn({ user }) {
      if (user.email?.endsWith("@mycompany.com")) {
        return true
      }
      return false // Deny login
    }
  }
})
      

3. Mounting the Route Handlers

Auth.js requires API routes to handle the OAuth redirects, token exchange handshakes, and signout commands. Under the Next.js App directory, create a Catch-All route handler at app/api/auth/[...nextauth]/route.ts.

        import { handlers } from "@/auth"

// Export NextAuth handlers for both GET and POST requests
export const { GET, POST } = handlers
      

It is quite literally two lines of code! By creating this file, routes like /api/auth/signin and /api/auth/callback/github automatically become available to the client.

4. Protecting Server Components

The true power of Next.js 14+ is Server Components. We can perform session verification securely on the server before a single byte of HTML is sent to the user.

        import { auth } from "@/auth"
import { redirect } from "next/navigation"
import { performSecureDatabaseQuery } from "@/lib/db"

export default async function DashboardPage() {
  // Retrieve the session from the HTTP-only cookie
  const session = await auth()
  
  // If no session exists, forcefully bounce the user to login
  if (!session?.user) {
    redirect('/api/auth/signin')
  }

  // Because this is a server component, we can safely fetch data
  // using the validated session ID directly inside the component
  const secureData = await performSecureDatabaseQuery(session.user.id)

  return (
    <main class="p-8">
      <header class="flex items-center gap-4">
        <img 
          src={session.user.image} 
          alt="Profile Avatar" 
          class="w-12 h-12 rounded-full ring-2 ring-violet-500"
        />
        <h1 class="text-3xl font-bold">
          Welcome to the Vault, {session.user.name}
        </h1>
      </header>
      
      
    </main>
  )
}
      

5. Protecting Routes via Middleware

If you have an entire sub-directory (like /admin/) that needs protection, doing it on a per-component basis is tedious. Next.js Middleware acts as a gatekeeper. By exporting Auth.js into your middleware.ts file, you can instantly protect hundreds of routes globally.

        // middleware.ts
export { auth as middleware } from "@/auth"

// Automatically invoke middleware on specific paths
export const config = {
  matcher: ["/admin/:path*", "/dashboard/:path*"]
}
      

Conclusion

Stop writing authentication flow from scratch. The transition to OAuth providers eliminates password fatigue for your users, and pushing the verification logic into Auth.js eliminates security paranoia for you. By leveraging these patterns in Next.js Server Components, you ensure that unauthorized users are bounced before your UI ever renders.

Explore Your Brain Editorial Team

About Explore Your Brain Editorial Team

Science Communication

Our editorial team consists of science writers, researchers, and educators dedicated to making complex scientific concepts accessible to everyone. We review all content with subject matter experts to ensure accuracy and clarity.

Science Communication CertifiedPeer-Reviewed by Domain ExpertsEditorial Standards: AAAS GuidelinesFact-Checked by Research Librarians

Frequently Asked Questions

Do I need a database to use Auth.js?

No. If you only want to use JSON Web Tokens (JWT) for session management (e.g., verifying a user logged in via Google but not permanently saving their profile), no database is required. However, if you want to store user data permanently or use database-backed sessions instead of cookies, you will need to configure an adapter.

How does Auth.js handle session security?

By default, Auth.js uses encrypted, HTTP-only, SameSite cookies. This means the session token cannot be accessed by client-side JavaScript, rendering XSS (Cross-Site Scripting) attacks against the session token virtually impossible.

Can I use classic username and password login?

Yes, via the Credentials Provider. However, Auth.js heavily discourages this because it places the burden of password hashing, salting, database security, and reset flows entirely on you. OAuth providers (Google, GitHub) or Magic Links are fundamentally more secure.

References