Skip to main content

Authentication

VizBoard implements a robust authentication system using NextAuth.js v5 with multiple authentication strategies.

Authentication Providers

VizBoard supports three authentication methods:

Credentials

Email and password authentication with bcrypt hashing

Google OAuth

OAuth 2.0 authentication via Google

GitHub OAuth

OAuth 2.0 authentication via GitHub

Session Management

Sessions are managed using JWT (JSON Web Tokens) with the following configuration:
src/lib/auth/auth.ts
session: {
  strategy: "jwt",
  maxAge: 30 * 24 * 60 * 60, // 30 days
}
JWT sessions are stateless and don’t require database lookups on every request, improving performance while maintaining security.

Password Security

Passwords are hashed using bcrypt with a salt rounds value of 10:
const hashedPassword = await bcrypt.hash(password, 10)

await prisma.user.create({
  data: {
    firstName,
    lastName,
    email,
    password: hashedPassword,
    name: fullName,
  },
})
Never store plain-text passwords. Always use bcrypt or similar one-way hashing algorithms with appropriate salt rounds.

Authorization

Route Protection

VizBoard uses NextAuth middleware to protect routes automatically:
src/app/middleware.ts
export { auth as middleware } from "@/lib/auth/auth"
This middleware runs on every request and ensures users are authenticated before accessing protected routes.

API Route Authorization

API routes verify user sessions and enforce ownership-based access control:
src/app/api/widgets/[widgetId]/route.ts
const session = await auth()
if (!session?.user?.id) {
  return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}

const widget = await prisma.widget.findFirst({
  where: {
    id: widgetId,
    project: {
      userId: session.user.id, // Ownership check
    },
  },
})
Every resource query includes a userId check to ensure users can only access their own data.

Server Action Authorization

Server actions verify both project existence and user ownership:
src/app/actions/project/validation.ts
const project = await prisma.project.findFirst({
  where: { id: projectId, userId },
  include: {
    dbconnections: true,
  },
})

if (!project) {
  return { success: false, error: "Project not found or unauthorized" }
}

Input Validation

Schema Validation with Zod

All user inputs are validated using Zod schemas before processing:
export const LoginSchema = z.object({
  email: z.string().email({
    message: "Please enter a valid email address",
  }),
  password: z.string().min(8, {
    message: "Password must be at least 8 characters long",
  }),
})

Validation in Server Actions

src/app/actions/project/validation.ts
if (!projectId || typeof projectId !== "string") {
  return {
    success: false,
    error: "Valid project ID is required",
  }
}

if (!userId || typeof userId !== "string") {
  return {
    success: false,
    error: "Valid user ID is required",
  }
}

Environment Variables

Required Security Variables

VizBoard requires the following environment variables for secure operation:
VariablePurposeFormat
ENCRYPTION_KEYAES-256-GCM encryption key for database credentials64-character hex string (32 bytes)
AUTH_GOOGLE_IDGoogle OAuth client IDString
AUTH_GOOGLE_SECRETGoogle OAuth client secretString
AUTH_GITHUB_IDGitHub OAuth client IDString
AUTH_GITHUB_SECRETGitHub OAuth client secretString
NEXTAUTH_URLBase URL for authentication callbacksURL
NEXTAUTH_SECRETSecret for signing tokensString
Critical Security Requirements:
  • Never commit .env files to version control
  • Use different encryption keys for development and production
  • Rotate secrets regularly
  • Use strong, randomly generated values for all secrets

Environment Variable Validation

The application validates required environment variables at startup:
src/lib/crypto/crypto.ts
const ENCRYPTION_KEY_HEX = process.env.ENCRYPTION_KEY

if (!ENCRYPTION_KEY_HEX) {
  throw new Error("Missing ENCRYPTION_KEY environment variable. It should be a 64-char hexadecimal string.")
}

const ENCRYPTION_KEY = Buffer.from(ENCRYPTION_KEY_HEX, 'hex')

if (ENCRYPTION_KEY.length !== 32) {
  throw new Error(`ENCRYPTION_KEY must be 32 bytes (64 hex characters) long. Current length: ${ENCRYPTION_KEY.length} bytes.`)
}
src/lib/auth/auth.ts
if (!process.env.AUTH_GOOGLE_ID || !process.env.AUTH_GOOGLE_SECRET) {
  throw new Error("Missing environment variables for Google OAuth")
}
if (!process.env.AUTH_GITHUB_ID || !process.env.AUTH_GITHUB_SECRET) {
  throw new Error("Missing environment variables for Github OAuth")
}

Security Best Practices

1. Data Encryption

Sensitive Data Protection

All database credentials are encrypted at rest using AES-256-GCM. See the Encryption page for implementation details.

2. Error Handling

Avoid leaking sensitive information in error messages:
src/lib/auth/auth.ts
if (!user) {
  if (process.env.NODE_ENV !== "production") {
    console.error(
      "Login with credentials failed: User not found with email:",
      inputEmail
    )
  }
  throw new Error(ERROR_INVALID_CREDENTIALS) // Generic error message
}
Detailed error logs are only shown in development. Production returns generic error messages to prevent information disclosure.

3. OAuth vs Credentials

The system prevents credential-based login for OAuth users:
src/lib/auth/auth.ts
if (!user.password) {
  throw new Error(
    "This email is registered with Google or GitHub. Please try signing in with them."
  )
}

4. Session Token Management

JWT tokens are refreshed on each request and include user data:
src/lib/auth/auth.ts
async jwt({ token, user, account }) {
  if (user) {
    token.id = user.id
    token.email = user.email
    token.name = user.name
    token.firstName = user.firstName
    token.lastName = user.lastName
    token.sub = user.id
  } else if (token.id) {
    // Verify user still exists in database
    const dbUser = await prisma.user.findUnique({
      where: { id: token.id as string },
    })
    if (!dbUser) {
      return null // Invalidate token
    }
  }
  return token
}

5. Email Normalization

All email addresses are normalized to lowercase to prevent duplicate accounts:
src/app/actions/auth/signUp.ts
const email = data.email.toLowerCase()

Encryption

Learn about AES-256-GCM encryption for database credentials

API Development

Build secure API endpoints with proper authorization

Build docs developers (and LLMs) love