Authentication

Implementing authentication in ShipKit with NextAuth.js v5

Authentication

ShipKit uses NextAuth.js v5 for authentication, providing a secure and flexible authentication system with support for multiple providers and custom flows.

Setup

Configuration

// src/lib/auth/auth.ts
import NextAuth from "next-auth"
import { authConfig } from "./auth.config"

export const {
  auth,
  signIn,
  signOut,
  handlers: { GET, POST }
} = NextAuth(authConfig)

Error Handling

ShipKit provides comprehensive error handling for authentication flows:

// src/app/(app)/(authentication)/error/page.tsx
enum Error {
  Configuration = "Configuration",
  // Add other error types here
}

const errorMap = {
  [Error.Configuration]: (
    <p>
      There was a problem when trying to authenticate. Please contact us if this
      error persists. Unique error code:{" "}
      <code className="rounded-sm bg-slate-100 p-1 text-xs">Configuration</code>
    </p>
  ),
  // Add other error messages here
}

Sign In Flow

The sign-in process includes both form-based and OAuth authentication:

// src/app/(app)/(authentication)/sign-in/_components/sign-in-form.tsx
export const SignInForm = () => {
  const form = useForm<z.infer<typeof signInSchema>>({
    resolver: zodResolver(signInSchema),
    defaultValues: getSchemaDefaults(signInSchema),
  })

  return (
    <Form {...form}>
      <form className="space-y-8">
        <FormField
          control={form.control}
          name="email"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Email</FormLabel>
              <FormControl>
                <Input placeholder="[email protected]" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />
        {/* Password field */}
        <Button type="submit" className="w-full">
          Login
        </Button>
      </form>
    </Form>
  )
}

OAuth Providers

// src/app/(app)/(authentication)/_components/oauth-buttons.tsx
export const OAuthButtons = () => {
  return (
    <div className="space-y-4">
      <Button onClick={() => signIn("google")}>
        Continue with Google
      </Button>
      <Button onClick={() => signIn("github")}>
        Continue with GitHub
      </Button>
    </div>
  )
}

Sign Out Flow

The sign-out process includes a confirmation step and proper session cleanup:

// src/app/(app)/(authentication)/sign-out/page.tsx
export default function SignOutPage() {
  return (
    <div className="space-y-4">
      <h1>Sign Out</h1>
      <p>Are you sure you want to sign out?</p>
      <Button onClick={() => signOut()}>
        Yes, sign me out
      </Button>
    </div>
  )
}

Password Reset Flow

The password reset process includes:

  1. Request reset email
  2. Validate reset token
  3. Set new password
// src/app/(app)/(authentication)/forgot-password/page.tsx
export default function ForgotPasswordPage() {
  return (
    <div className="space-y-4">
      <h1>Reset Password</h1>
      <ForgotPasswordForm />
    </div>
  )
}

Role-Based Access Control

ShipKit implements role-based access control through middleware:

// src/middleware.ts
export const config = {
  matcher: [
    "/dashboard/:path*",
    "/admin/:path*",
    "/api/:path*",
  ],
}

export default authMiddleware({
  callbacks: {
    authorized: ({ auth, request }) => {
      // Public routes
      if (request.nextUrl.pathname === "/") return true

      // Protected routes
      if (!auth?.user) return false

      // Admin routes
      if (
        request.nextUrl.pathname.startsWith("/admin") &&
        !auth.user.roles.includes("ADMIN")
      ) {
        return false
      }

      return true
    },
  },
})

Security Best Practices

  1. CSRF Protection: Built-in CSRF protection through NextAuth.js
  2. Session Management: Secure session handling with JWT or database sessions
  3. Rate Limiting: Implementation of rate limiting on auth endpoints
  4. Password Security: Strong password requirements and secure storage
  5. OAuth Security: Proper configuration of OAuth providers and callback URLs

Error States

The authentication system handles various error states:

  1. Invalid credentials
  2. Account not found
  3. Email not verified
  4. OAuth provider errors
  5. Rate limiting errors
  6. Network errors
  7. Configuration errors

Testing

// src/tests/auth.test.ts
describe("Authentication", () => {
  it("should handle sign in", async () => {
    // Test sign in flow
  })

  it("should handle sign out", async () => {
    // Test sign out flow
  })

  it("should handle password reset", async () => {
    // Test password reset flow
  })
})

API Reference

Sign In

POST /api/auth/signin
Content-Type: application/json

{
  "email": "[email protected]",
  "password": "password123"
}

Sign Out

POST /api/auth/signout

Password Reset

POST /api/auth/forgot-password
Content-Type: application/json

{
  "email": "[email protected]"
}

Environment Variables

# OAuth
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=

# Auth
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=your-secret-key

# Email (for password reset)
EMAIL_SERVER_HOST=
EMAIL_SERVER_PORT=
EMAIL_SERVER_USER=
EMAIL_SERVER_PASSWORD=
EMAIL_FROM=