惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

P
Proofpoint News Feed
C
CERT Recently Published Vulnerability Notes
O
OpenAI News
V
Vulnerabilities – Threatpost
C
Cybersecurity and Infrastructure Security Agency CISA
S
Schneier on Security
Latest news
Latest news
F
Full Disclosure
T
Tenable Blog
T
Troy Hunt's Blog
The Last Watchdog
The Last Watchdog
S
Secure Thoughts
L
LangChain Blog
有赞技术团队
有赞技术团队
Project Zero
Project Zero
Cloudbric
Cloudbric
爱范儿
爱范儿
GbyAI
GbyAI
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
The Exploit Database - CXSecurity.com
S
Security @ Cisco Blogs
Hugging Face - Blog
Hugging Face - Blog
Recorded Future
Recorded Future
大猫的无限游戏
大猫的无限游戏
Last Week in AI
Last Week in AI
C
Cisco Blogs
WordPress大学
WordPress大学
Apple Machine Learning Research
Apple Machine Learning Research
小众软件
小众软件
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
V2EX - 技术
V2EX - 技术
Engineering at Meta
Engineering at Meta
Spread Privacy
Spread Privacy
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Hacker News: Ask HN
Hacker News: Ask HN
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Schneier on Security
Schneier on Security
T
Threat Research - Cisco Blogs
M
MIT News - Artificial intelligence
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
K
Kaspersky official blog
The Hacker News
The Hacker News
V
V2EX
F
Fortinet All Blogs
L
LINUX DO - 最新话题
Cisco Talos Blog
Cisco Talos Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
N
News | PayPal Newsroom
博客园 - 三生石上(FineUI控件)
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org

Clerk Blog

Going to production with Clerk Deploy Clerk Init: The fastest way to start a new project Introducing Clerk CLI Middleware-based route protection bypass Postmortem: Clerk System Outage (March 10, 2026) Clerk for the AI era Add API Key support to your SaaS in minutes Postmortem: Clerk System Outage (February 19, 2026) Using Clerk in a React Native app Postmortem: DNS Provider Outage (February 10, 2026) How do I implement passkeys in Next.js? Clerk ranked #4 fastest-growing software vendor on Ramp’s December 2025 list How do I handle JWT verification in Next.js? Committing to Agent Identity: Clerk raises $50m Series C from Menlo and Anthropic’s Anthology Fund What is the best way to handle authentication in Next.js App Router? Postmortem: Database Incident (September 14–18, 2025) How do I add authentication to a Next.js app? Introducing Free Trials in Clerk Billing Postmortem: August 28, 2025 - elevated API latency and errors Introducing Mosaic: Bring Your Brand to Every Authentication Flow Multi-tenant authentication: What you need to know (and how Clerk helps) What are the risks and challenges of multi-tenancy? Resilience in Practice: Regional Failover at Clerk Build a Cross-Platform B2B App with Clerk, Expo, and Supabase Highlights from the MiduDev/Clerk Hackathon Add multi-tenancy to an app built with Clerk, Lovable, and Supabase How to build an AI coding rules app with Clerk, Lovable, and Supabase How to Build Multi-Tenant Authentication with Clerk Choosing the right SaaS architecture: Multi-Tenant vs. Single-Tenant Postmortem: June 26, 2025 service outage How to Design a Multi-Tenant SaaS Architecture What is multi-tenancy and why it matters for B2B SaaS How OAuth Works Synchronize user data from Clerk to Supabase Add subscriptions to your SaaS with Clerk Billing Getting started with Clerk Billing Multi-tenant analytics with Tinybird and Clerk How Huntr Migrated 250K Users to Clerk: A Scalable Auth Solution for Startups How to take Clerk to Production How to take your Clerk application to production A practical guide to testing Clerk Next.js applications Implementing multi-tenancy into a Supabase app with Clerk How Clerk integrates with a Next.js application using Supabase How Clerk integrates with Supabase Build a blog with tRPC, Prisma, Next.js and Clerk How to enrich PostHog events with Clerk user data Validate your SaaS idea while building an audience Postmortem: February 6, 2025 service outage Implement Role-Based Access Control in Next.js 15 Build a Next.js sign-up form with React Hook Form Build a Next.js login page template How to implement Google authentication in Next.js 15 What is middleware in Next.js? How to customize Next.js metadata How to set environment variables in Node.js Building a React Login Page Template How to implement per-user OAuth scopes with Clerk Using Clerk SSO to access Google Calendar and other service data Streamline enterprise customer onboarding with SAML and Clerk Clerk launches EASIE SSO and eliminates SSO fees How to secure Liveblocks Rooms with Clerk in Next.js Securing Node.js Express APIs with Clerk and React Combining the benefits of session tokens and JWTs Build a task manager with Next.js, Supabase, and Clerk Comparing Clerk Webhooks vs Backend API Automate Neon schema changes with Drizzle and GitHub Actions A guide to reading authenticated user data from Clerk Role based access control with Clerk Organizations Mitigating OAuth’s recently discovered Open Response Type vulnerability Per-user B2B monetization with Stripe and Clerk Organizations Build a team-based task manager with Next.js, Neon, and Clerk Building a Hybrid Sign-Up/Subscribe Form with Stripe Elements Welcoming Colin from Zod as our inaugural Open Source Fellow Build a modern authenticated chat application with Next.js, Ably, and Clerk Build a waitlist with Clerk user metadata How to use Clerk with PostHog Identify in Next.js How to secure API Gateway using JWT and Lambda Authorizers with Clerk What are passkeys and how do they work? Comparing Authentication in React.js vs. Next.js How to Add an Onboarding Flow for your Application with Clerk Create Your Own Custom User Menu with Radix - Part 2 Introducing Webhook Workflows with Inngest & Svix Clerk raises $30M Series B from CRV and Stripe Clerk in 2023: A Year in Review Build a Movie Emoji Quiz App with Remix, Fauna, and Clerk Ultimate Guide to Magic Link Authentication Create Your Own Custom User Menu with Radix Introducing has(), protect(), and <Protect> Updated Pricing: 10,000 MAUs Free, and a new “Pro Plan” Next.js Authentication with Clerk: Streamlined SSR Handling Clerk Webhooks: Data Sync with Convex Exploring Clerk Metadata with Stripe Webhooks The Ultimate Guide to Next.js Authentication Empower Your Support Team With User Impersonation Clerk Webhooks: Getting Started A Complete Guide to Session Management in Next.js The Advanced Guide to Passwordless Authentication in Next.js How We Roll – Chapter 10: Roundup How We Roll – Chapter 9: Infrastructure Password-Based Authentication in Next.js
How to build a secure project management platform with Next.js, Clerk, and Neon
Brian Morrison II · 2025-02-20 · via Clerk Blog

Around 30,000 websites and applications are hacked every day*, and the developer is often to blame.

The vast majority of breaches occur due to misconfiguration rather than an actual vulnerability. This could be due to exposed database credentials, unprotected API routes, or data operations without the proper authorization checks just to name a few. It’s important to ensure that your application is configured in a way that prevents attackers from gaining unauthorized access to user data.

In this article, you’ll learn how to build a project management web application while considering security best practices throughout.

Although this article can be followed by itself, it is the second in a series covering the process of building Kozi - a collaborative project and knowledge management tool. Throughout the series, the following features will be implemented:

  • Create organizations to invite others to manage projects as a team.
  • A rich, collaborative text editor for project and task notes.
  • A system to comment on projects, tasks, and notes.
  • Automatic RAG functionality for all notes and uploaded files.
  • Invite users from outside your organization to collaborate on individual tasks.
  • Be notified when events occur on tasks you subscribe to, or you are mentioned in comments or notes.

What makes this a “secure” project management system?

Data security is considered throughout this guide by using the following techniques:

Clerk and the Next.js middleware

Clerk is a user management platform for Next.js designed to get authentication into your application as quick as possible by providing a complete suite of user management tools as well as drop-in UI components. Behind the scenes, Clerk creates fast expiring tokens upon user sign-in that are sent to your server with each request, where Clerk also verifies the identify of the user.

Clerk integrates with Next.js middleware to ensure every request to the application is evaluated before it reaches its destination. In the section where the middleware is configured, we instruct the middleware to protect any route starting with /app so that only authenticated users may access them. This means that before any functions are executed (on the client or server), the user will need to be authenticated.

Server actions

In this project, server actions are the primary method of interacting with the data in the database. Direct access to the database should always happen on the server and NEVER on the client where tech-savvy users can gain access to the database credentials. Since all functions that access the database are built with server actions, they do not execute client-side.

It's important to note that calling these server actions should only ever be performed from protected routes. When a Next.js client component executes a server action, an HTTP POST request of form data is submitted to the current path with a unique identifier of the action for Next.js to route the data internally.

This means that calling a server function from an anonymous route might result in anonymous users getting access to the data. This potential vulnerability is addressed in the next section.

Database requests

Protecting access to the functions is only one consideration. Each request will have an accompanying user identifier which can be used to determine the user making that request. This identifier is stored alongside the records the user creates, allowing each request for data to ONLY return the data associated with that user.

When making data modifications, the requesting user ID is cross-referenced with the records being modified or deleted so that one user cannot affect another user’s data.

The combination of protecting access to the routes, being mindful of calling server actions, and cross-referencing database queries with the user making the request ensures that the data within the application is secure and only accessible to those who have access to it.

How to follow along

Kozi is an open-source project, with each article in the series having corresponding start and end branches. This makes it easy to jump in at any point to get hands-on experience with the concepts outlined in each piece, as well as a point of reference if you simply want to see the completed code. Here are links to the specific branches:

You should have a basic understanding of Next.js and React as well.

Launching the project

Once the branch above is cloned, open the project in your editor or terminal and run the following command to start up the application:

Open your browser and navigate to the URL displayed in the terminal to access Kozi. At the bottom right of the screen, you should see Clerk is running in keyless mode. Click the button to claim your keys and associate this instance to your Clerk account. If you don’t have an account, you’ll be prompted to create one.

Claim your Clerk keys

You are now ready to start building out the core functionality of Kozi!

Setting up the database

To store structured data, you’ll be using a serverless instance of Postgress provided by Neon. Start by heading to neon.tech and creating an account if you don’t have one. Create a new database and copy the connection string as shown below.

Copy the connection string

Create a new file in your local project named .env.local and paste the following snippet, replacing the placeholder for your specific Neon database connection string.

Configuring Prisma

Prisma is used as the ORM to access and manipulate data in the database, as well as apply schema changes to the database as the data needs are updated. Open the project in your IDE and start by creating the schema file at prisma/schema.prisma. Paste in the following code:

prisma/schema.prisma

Next, create the src/lib/db.ts file and paste in the following code which will be used throughout the application to create a connection to the database:

src/lib/db.ts

To sync the schema changes to Neon, run the following command in the terminal:

If you open the database in the Neon console and navigate to the Tables menu item, you should see the projects and tasks tables shown.

Neon tables

Finally, since it is not best practice to use the Prisma client in any client-side components, you’ll want a file to store interfaces so that TypeScript can recognize the structure of your objects when passing them between components.

Create the src/app/app/models.ts file and paste in the following:

src/app/app/models.ts

Configure /app as a protected route with Clerk

Clerk’s middleware uses a helper function called createRouteMatcher that lets you define a list of routes to protect. This includes any pages, server actions, or API handlers stored in the matching folders of the project.

All of the core functionality of the application will be stored in the /app route, so update src/middleware.ts to use the createRouteMatcher to protect everything in that folder:

src/middleware.ts

The /app route will use a different layout from the landing page, which will contain a collapsible sidebar that contains the <UserButton /> (a Clerk UI component that lets users manage their profile and sign out), an inbox for tasks, and a list of projects that tasks can be created in.

Start by creating the src/app/app/components/Sidebar.tsx file to render the elements of the sidebar:

src/app/app/components/Sidebar.tsx

Now create src/app/app/layout.tsx to render the sidebar with the pages in the /app route:

src/app/app/layout.tsx

Next, create src/app/app/page.tsx which is just a simple page that renders some text to make sure the /app route works as expected:

src/app/app/page.tsx

Open the application in your browser and test out the changes by navigating to the /app which should automatically redirect you to the /sign-in route where you can create an account and make sure /app only works when authenticated.

Working with tasks

At the core of every project is a list of tasks, so now we’ll configure the ability to create and work with tasks in the default Inbox list. Several components will be used to provide the following application structure. The following image shows how these components will be used:

Kozi UI diagram

These are all client components so they will need corresponding server actions so they can interact with the database securely. Create the src/app/app/actions.ts file and paste in the following code:

src/app/app/actions.ts

We’re going to start with the <CreateTaskInput /> component which renders the field where users can create tasks. Create the src/app/app/components/CreateTaskInput.tsx file and paste in the following:

src/app/app/components/CreateTaskInput.tsx

Next, we’ll move on to <TaskCard />, which will display the name of the task and allow users to toggle it using a checkbox, as is standard in task-centric applications. Create the src/app/app/components/TaskCard.tsx file and paste in the following:

src/app/app/components/TaskCard.tsx

Finally, create the <TaskList /> component to render the list of tasks and the input to create new ones. Create the src/app/app/components/TaskList.tsx file and paste in the following:

src/app/app/components/TaskList.tsx

With all of our components created, update the src/app/app/page.tsx to match the following code which uses the components created above, as well as queries the database for all tasks on load:

src/app/app/page.tsx

If you access the application again, you can now create tasks in your inbox and complete them.

Editing and deleting tasks

Now that you can create tasks, the next step is to set up a modal so clicking the task (outside of the checkbox) will display the modal and allow you to change the name of the task and set a description if needed.

As a design decision, this modal does not include a save button but rather debounces any edits for 1 second to create an experience where users can quickly save values and avoid another click. The modal will also create a menu in the header which allows you to delete the task.

Start by appending the following code to src/app/app/actions.ts:

src/app/app/actions.ts

Next, create the src/app/app/components/EditTaskModal.tsx and paste in the following:

src/app/app/components/EditTaskModal.tsx

Finally, update src/app/app/TaskCard.tsx to include the EditTaskModal component and handle user click events:

src/app/app/TaskCard.tsx

Now you can click anywhere outside of the checkbox of a task to open the modal to edit the task name and description or delete the task from the database.

Working with projects

Users of Kozi can create projects to organize their tasks into categorized lists. Projects will be listed in the sidebar in their own section from the Inbox. When selected, the user will navigate to the /app/projects/[_id] route to see the tasks for that project. To start implementing this, update src/app/app/actions.ts to match the following:

src/app/app/actions.ts

Next, you’ll need to create the page to render the tasks for a given project. Create src/app/app/projects/[_id]/page.tsx and paste in the following:

src/app/app/projects/[_id]/page.tsx

Notice in the TaskList component that we’ve added projectId to the list of props. This is so that the currently active project ID can be passed to CreateTaskInput so that when a task is created, it knows what project to associate it with. Let’s update those two components now.

Modify app/src/src/components/CreateTaskInput.tsx to match the following:

app/src/src/components/CreateTaskInput.tsx

Next, update the TaskCard component to pass the name of the selected project through to the EditTaskModal to provide a quick reference to what project the task is part of.

Edit src/app/app/components/TaskCard.tsx to match the following:

src/app/app/components/TaskCard.tsx

Now update src/app/app/components/TaskList.tsx to include the projectId prop and pass it to CreateTaskInput:

src/app/app/components/TaskList.tsx

In order to access project data in real time from multiple client-side components, we’re going to use a Zustand store to keep things synchronized throughout the application. Using a store will allow projects to be edited and deleted without having to refresh the page. This will become more evident in the subsequent sections.

Create src/lib/store.ts and paste in the following:

src/lib/store.ts

The projects will be listed in the sidebar, alongside a button to create new projects as needed. Each element in the list will be its own component. Create src/app/app/components/ProjectLink.tsx and paste in the following:

src/app/app/components/ProjectLink.tsx

Let’s create a component that will live in the sidebar that opens a modal to create a new project. Create the src/app/app/components/CreateProjectButton.tsx file and paste in the following:

src/app/app/components/CreateProjectButton.tsx

Finally, you’ll update the sidebar to query the list of projects and populate the store when the component renders. Update src/app/app/components/Sidebar.tsx to match the following:

src/app/app/components/Sidebar.tsx

You can now add projects from the sidebar and add tasks to those projects.

Editing and deleting projects

Following the same design approach as earlier, we’ll now update the project page so that users can simply click the name of a project to edit it. We’ll also debounce the save so there is no need to manually click a save button. Because a Zustand store is being used, updating the name of the project in the store will automatically cause the new name to be displayed in the sidebar without having to refresh the page.

Start by appending the following server actions to src/app/app/actions.ts:

src/app/app/actions.ts

Since the project name is rendered in the <TaskList /> component, update src/app/app/components/TaskList.tsx to match the following:

src/app/app/components/TaskList.tsx

To delete projects, we’ll use the same approach as we did with tasks by rendering a dropdown menu with an option to delete the project. Instead of in a modal though, we’ll add it to the <ProjectLink /> component so that when the user hovers over a project in the sidebar, the menu icon will be displayed as a clickable button.

Update src/app/app/components/ProjectLink.tsx to match the following code:

src/app/app/components/ProjectLink.tsx

You can now update the names of projects and delete them as needed. Deleting a project will also delete any associated tasks with that project.

Conclusion

When building any application, security should always be something considered early on in the process. By considering the principles laid out in this article, you can build a secure system with ease using Clerk and properly structuring the code that accesses your database.

In the next article of the series, we’ll explore how you can securely access the data within your Neon database from the front end using Row Level Security using Clerk.

* Source: How Many Cyber Attacks Per Day: The Latest Stats and Impacts in 2025