Skip to main content

Overview

VulnTrack uses NextAuth.js for authentication, configured with JWT sessions and credentials-based login. All server actions automatically validate the user session before executing.

Authentication Configuration

The auth configuration is defined in src/lib/auth.ts:
import { NextAuthOptions } from "next-auth"
import CredentialsProvider from "next-auth/providers/credentials"
import { PrismaAdapter } from "@next-auth/prisma-adapter"

export const authOptions: NextAuthOptions = {
  adapter: PrismaAdapter(prisma),
  session: {
    strategy: "jwt",
  },
  pages: {
    signIn: "/login",
  },
  providers: [
    CredentialsProvider({
      name: "Credentials",
      credentials: {
        email: { label: "Email", type: "email" },
        password: { label: "Password", type: "password" }
      },
      async authorize(credentials) {
        // Validates credentials and returns user object
      }
    })
  ],
  callbacks: {
    async session({ session, token }) {
      return {
        ...session,
        user: {
          ...session.user,
          id: token.id,
          role: token.role,
          isOnboarded: token.isOnboarded,
        }
      }
    },
    async jwt({ token, user }) {
      // Includes user metadata in JWT
      // Refreshes data from database on each request
    }
  }
}

Session Object

The session object contains:
user.id
string
Unique user identifier
user.email
string
User’s email address
user.name
string
User’s display name
user.role
'ADMIN' | 'CONTRIBUTOR' | 'VIEWER'
User’s role within their team
user.isOnboarded
boolean
Whether the user has completed onboarding

Getting the Current Session

In Server Actions

import { getServerSession } from "next-auth"
import { authOptions } from "@/lib/auth"

export async function protectedAction() {
  const session = await getServerSession(authOptions)

  if (!session?.user?.id) {
    return { success: false, error: "Unauthorized" }
  }

  // Action logic here
  return { success: true, data: result }
}

In Server Components

import { getServerSession } from "next-auth"
import { authOptions } from "@/lib/auth"

export default async function ProtectedPage() {
  const session = await getServerSession(authOptions)

  if (!session) {
    redirect('/login')
  }

  return <div>Welcome, {session.user.name}</div>
}

In Client Components

'use client'

import { useSession } from 'next-auth/react'

export function UserProfile() {
  const { data: session, status } = useSession()

  if (status === 'loading') return <div>Loading...</div>
  if (status === 'unauthenticated') return <div>Not logged in</div>

  return <div>Hello, {session.user.name}</div>
}

Authentication Actions

registerUser

Register a new user account.
The first user automatically becomes an ADMIN. Subsequent users require an invitation token or create a new team as ADMIN.
import { registerUser } from '@/app/actions/auth'

const result = await registerUser({
  email: '[email protected]',
  password: 'SecurePass123!',
  name: 'John Doe',
  token: 'invitation-token' // Optional
})
email
string
required
User’s email address
password
string
required
Password meeting security requirements:
  • At least 8 characters
  • 1 uppercase letter
  • 1 lowercase letter
  • 1 number
  • 1 special character (@$!%*?&)
name
string
required
User’s display name
token
string
Optional invitation token for joining an existing team
Response:
success
boolean
Whether registration succeeded
data.id
string
The created user’s ID
data.email
string
The user’s email
error
string
Error message if registration failed

Password Policy

Passwords must meet these requirements:
  • Minimum 8 characters
  • At least one uppercase letter (A-Z)
  • At least one lowercase letter (a-z)
  • At least one digit (0-9)
  • At least one special character (@$!%*?&)
Validation is enforced server-side:
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/
if (!passwordRegex.test(password)) {
  return { success: false, error: "Password does not meet requirements" }
}

Rate Limiting

Registration attempts are rate-limited to prevent abuse:
  • Limit: 5 attempts per minute per IP
  • Window: 60 seconds
import { rateLimit } from "@/lib/rate-limit"

if (!rateLimit("registration_attempt", 5, 60000)) {
  return { success: false, error: "Too many attempts. Please try again later." }
}

Role-Based Access Control

VulnTrack has three user roles:

ADMIN

  • Full access to all team resources
  • Can invite users and assign roles
  • Can approve pending vulnerabilities
  • Can assign vulnerabilities to team members
  • Manage team settings

CONTRIBUTOR

  • Create vulnerabilities (require approval)
  • Edit own vulnerabilities
  • View approved vulnerabilities
  • Add comments

VIEWER

  • View approved vulnerabilities
  • Add comments
  • Read-only access to reports

Protecting Server Actions

All server actions should validate both authentication and authorization:
export async function updateVulnerability(id: string, data: any) {
  // 1. Check authentication
  const session = await getServerSession(authOptions)
  if (!session?.user?.id) {
    return { success: false, error: "Unauthorized" }
  }

  // 2. Check authorization
  const user = await prisma.user.findUnique({
    where: { id: session.user.id },
    select: { role: true, teamId: true }
  })

  const existing = await prisma.vulnerability.findUnique({
    where: { id }
  })

  // 3. Verify team isolation
  if (existing.teamId !== user?.teamId) {
    return { success: false, error: "Unauthorized" }
  }

  // 4. Verify ownership or admin role
  if (existing.userId !== session.user.id && user?.role !== 'ADMIN') {
    return { success: false, error: "Unauthorized" }
  }

  // Proceed with update
}

Session Refresh

JWT tokens are automatically refreshed with fresh user data from the database:
async jwt({ token, user }) {
  if (token.id) {
    const freshUser = await prisma.user.findUnique({
      where: { id: token.id },
      select: { role: true, isOnboarded: true }
    })
    if (freshUser) {
      token.role = freshUser.role
      token.isOnboarded = freshUser.isOnboarded
    }
  }
  return token
}
This ensures role changes take effect immediately without requiring re-login.

Next Steps

User Management

Manage users, invitations, and roles

Vulnerabilities

Work with vulnerability records

Build docs developers (and LLMs) love