Skip to content

08:File Uploads#7

Merged
CodingWithTushar merged 1 commit into
mainfrom
08-File-Uploads
Jul 22, 2026
Merged

08:File Uploads#7
CodingWithTushar merged 1 commit into
mainfrom
08-File-Uploads

Conversation

@CodingWithTushar

@CodingWithTushar CodingWithTushar commented Jul 22, 2026

Copy link
Copy Markdown
Owner
  • Add upload UI/components that let users choose and submit files.

  • Add server endpoint(s) to receive file uploads and perform validation (size, type).

  • Add storage handling (local storage / storage adapter) and safe filename handling.

  • Add tests exercising upload success and common failure cases.

  • Add documentation and an example showing how to use the uploads feature.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The upload page now submits video metadata and files to /api/upload-video. Clerk middleware adds public, protected, API, and admin routing behavior. The user helper awaits authentication, and Axios plus a form data interface are added.

Changes

Video upload flow

Layer / File(s) Summary
Upload form submission
types/interfaces.ts, package.json, app/(app)/uploadvideo/page.tsx
The client page collects title, description, and a video file, enforces a 70MB limit, builds multipart FormData, and posts it with Axios while tracking upload state.

Authentication and routing

Layer / File(s) Summary
Route authentication and authorization
middleware.ts
Clerk middleware matches public, API, and admin routes, redirects unauthorized page requests, returns 401 for protected API requests, and continues authorized requests.
User authentication helper update
utils/helpers.ts
requireUser awaits auth(), logs the resolved user ID, preserves its authorization check, and updates Cloudinary imports.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant UploadVideo
  participant axios
  participant UploadVideoAPI
  User->>UploadVideo: Select video and enter metadata
  UploadVideo->>UploadVideo: Validate file size and build FormData
  UploadVideo->>axios: POST FormData to /api/upload-video
  axios->>UploadVideoAPI: Submit multipart upload request
  UploadVideoAPI-->>axios: Return response status
  axios-->>UploadVideo: Resolve or reject request
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive No pull request description was provided, so there is nothing substantive to evaluate. Add a brief description of the file-upload and middleware changes so reviewers can understand the intent.
Title check ❓ Inconclusive The title is related to the upload feature, but it is too generic and chapter-like to describe the actual changes clearly. Rename it to something specific like "Add video upload page and auth middleware" so the main change is clear at a glance.
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/`(app)/uploadvideo/page.tsx:
- Around line 60-100: Update the title, description, and video-file controls in
the upload form to include unique IDs, and set each corresponding label’s
htmlFor to the matching ID so labels correctly associate with their inputs.
- Around line 42-47: Update the upload handler in the page component around the
response status check and catch block to store a user-visible status or error
message: set a success message when the response status is 200, and set a
failure message in the catch path while retaining the existing error handling.
Render the stored message in the UI so users can see whether the upload
succeeded or failed.
- Around line 26-29: Update the upload handler in the route’s request-processing
function to validate the received file size against the existing MAX_FILE_SIZE
limit before invoking Cloudinary. Reject oversized files with the appropriate
client-error response and do not proceed to upload; keep the client-side check
as-is.

In `@middleware.ts`:
- Line 11: Remove "/uploadvideo" from the isPublicRoutes collection in
middleware.ts so the route is handled as protected and authenticated users can
access the upload flow without being redirected to /home.
- Around line 47-64: Update the unauthenticated request handling around isApiReq
so non-public API requests reach the existing JSON 401 response before any
sign-in redirect. Exclude API requests from the page redirect condition, while
preserving redirects for unauthenticated non-public page routes and allowing
public routes unchanged.
- Around line 18-22: Update the isAdminRoute matcher to use a leading slash for
the admin API pattern, ensuring requests under /api/admin/... are matched while
preserving the existing admin route patterns.
- Around line 24-39: Update the clerkMiddleware callback to await the provided
auth helper before reading authentication state, then use the returned auth
context for the unauthenticated check. Replace the unsupported userId.role
comparison in the isAdminRoute branch with Clerk’s authorization check, such as
auth.has({ role: "admin" }), while preserving the existing redirects.

In `@utils/helpers.ts`:
- Around line 7-8: Update callers of requireUser, especially the upload-image
route handler, to await the helper before starting protected work so
authentication completes and failures remain inside the handler’s existing
try/catch. Audit all other requireUser call sites and add await wherever the
returned promise is currently used without awaiting.
- Line 10: Remove the console.log call that logs userId from the request
handling flow, leaving the surrounding behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 16d898d9-562b-4d9e-9f4a-72308fef5924

📥 Commits

Reviewing files that changed from the base of the PR and between 0274faf and c13c475.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • app/(app)/uploadvideo/page.tsx
  • middleware.ts
  • package.json
  • types/interfaces.ts
  • utils/helpers.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 9

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/`(app)/uploadvideo/page.tsx:
- Around line 60-100: Update the title, description, and video-file controls in
the upload form to include unique IDs, and set each corresponding label’s
htmlFor to the matching ID so labels correctly associate with their inputs.
- Around line 42-47: Update the upload handler in the page component around the
response status check and catch block to store a user-visible status or error
message: set a success message when the response status is 200, and set a
failure message in the catch path while retaining the existing error handling.
Render the stored message in the UI so users can see whether the upload
succeeded or failed.
- Around line 26-29: Update the upload handler in the route’s request-processing
function to validate the received file size against the existing MAX_FILE_SIZE
limit before invoking Cloudinary. Reject oversized files with the appropriate
client-error response and do not proceed to upload; keep the client-side check
as-is.

In `@middleware.ts`:
- Line 11: Remove "/uploadvideo" from the isPublicRoutes collection in
middleware.ts so the route is handled as protected and authenticated users can
access the upload flow without being redirected to /home.
- Around line 47-64: Update the unauthenticated request handling around isApiReq
so non-public API requests reach the existing JSON 401 response before any
sign-in redirect. Exclude API requests from the page redirect condition, while
preserving redirects for unauthenticated non-public page routes and allowing
public routes unchanged.
- Around line 18-22: Update the isAdminRoute matcher to use a leading slash for
the admin API pattern, ensuring requests under /api/admin/... are matched while
preserving the existing admin route patterns.
- Around line 24-39: Update the clerkMiddleware callback to await the provided
auth helper before reading authentication state, then use the returned auth
context for the unauthenticated check. Replace the unsupported userId.role
comparison in the isAdminRoute branch with Clerk’s authorization check, such as
auth.has({ role: "admin" }), while preserving the existing redirects.

In `@utils/helpers.ts`:
- Around line 7-8: Update callers of requireUser, especially the upload-image
route handler, to await the helper before starting protected work so
authentication completes and failures remain inside the handler’s existing
try/catch. Audit all other requireUser call sites and add await wherever the
returned promise is currently used without awaiting.
- Line 10: Remove the console.log call that logs userId from the request
handling flow, leaving the surrounding behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 16d898d9-562b-4d9e-9f4a-72308fef5924

📥 Commits

Reviewing files that changed from the base of the PR and between 0274faf and c13c475.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • app/(app)/uploadvideo/page.tsx
  • middleware.ts
  • package.json
  • types/interfaces.ts
  • utils/helpers.ts
🛑 Comments failed to post (9)
app/(app)/uploadvideo/page.tsx (3)

26-29: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Enforce the file-size limit on the server.

This browser-only check is bypassable. The supplied app/api/upload-video/route.ts handler sends the received file to Cloudinary without checking its size, allowing oversized authenticated uploads and their associated storage/processing cost. Reject files over the limit in the route handler before uploading.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/`(app)/uploadvideo/page.tsx around lines 26 - 29, Update the upload
handler in the route’s request-processing function to validate the received file
size against the existing MAX_FILE_SIZE limit before invoking Cloudinary. Reject
oversized files with the appropriate client-error response and do not proceed to
upload; keep the client-side check as-is.

42-47: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Show upload success and failure in the UI.

Failures are only logged to the console, so users cannot tell whether the upload completed and may retry unnecessarily. Store and render a status/error message.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/`(app)/uploadvideo/page.tsx around lines 42 - 47, Update the upload
handler in the page component around the response status check and catch block
to store a user-visible status or error message: set a success message when the
response status is 200, and set a failure message in the catch path while
retaining the existing error handling. Render the stored message in the UI so
users can see whether the upload succeeded or failed.

60-100: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Associate each visible label with its input.

These <label> elements have no htmlFor, while their controls have no IDs. Screen readers cannot reliably identify the fields, and clicking a label does not focus its control. Add matching IDs and htmlFor values for title, description, and video file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/`(app)/uploadvideo/page.tsx around lines 60 - 100, Update the title,
description, and video-file controls in the upload form to include unique IDs,
and set each corresponding label’s htmlFor to the matching ID so labels
correctly associate with their inputs.
middleware.ts (4)

11-11: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not classify the upload page as public.

A signed-in user visiting /uploadvideo is redirected to /home, making the authenticated upload flow inaccessible. Remove /uploadvideo from isPublicRoutes so it is protected instead.

Also applies to: 42-45

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@middleware.ts` at line 11, Remove "/uploadvideo" from the isPublicRoutes
collection in middleware.ts so the route is handled as protected and
authenticated users can access the upload flow without being redirected to
/home.

18-22: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^|/)middleware\.ts$|package\.json$|node_modules/next/dist/docs/' || true

echo "== package versions =="
if [ -f package.json ]; then
  node -e "const p=require('./package.json'); console.log(JSON.stringify({dependencies:p.dependencies, devDependencies:p.devDependencies}, null, 2));" | rg -n 'next|clerk|typescript|node' -C 1 || true
fi

echo "== middleware =="
if [ -f middleware.ts ]; then
  nl -ba middleware.ts | sed -n '1,120p'
fi

echo "== clerk createRouteMatcher usages/docs =="
rg -n "createRouteMatcher|/api/admin|admin\\/|admin\\(" . || true

Repository: CodingWithTushar/learningsaas

Length of output: 773


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== middleware.ts lines =="
awk '{printf "%5d\t%s\n", NR, $0}' middleware.ts | sed -n '1,160p'

echo "== package.json relevant deps =="
node - <<'JS'
const p = require('./package.json');
console.log({
  dependencies: p.dependencies && Object.fromEntries(Object.entries(p.dependencies).filter(([k]) => k === '`@clerk/nextjs`' || k === 'next')),
  devDependencies: p.devDependencies && Object.fromEntries(Object.entries(p.devDependencies).filter(([k]) => k === '`@types/react`' || k === 'typescript'))
});
JS

echo "== next docs available under node_modules =="
if [ -d node_modules/next ]; then
  find node_modules/next/dist/docs -maxdepth 3 -type f | head -100 || true
  rg -n "middleware|createRouteMatcher|route|pattern|matcher|api/admin|admin" node_modules/next/dist/docs || true
else
  echo "node_modules/next not present"
fi

echo "== clerk createRouteMatcher package docs/readme snippets =="
if [ -d node_modules/@clerk/nextjs ]; then
  find node_modules/@clerk/nextjs -maxdepth 4 \( -name '*.md' -o -path '*dist*' -o -path '*README*' \) | head -100 || true
  rg -n "createRouteMatcher|api/admin|/api/admin|matcher|route" node_modules/@clerk/nextjs -g '*.js' -g '*.ts' -g '*.md' || true
else
  echo "node_modules/@clerk/nextjs not present"
fi

echo "== search for admin routes/usages =="
rg -n "createRouteMatcher|routeMatcher|/api/admin|api/admin|/admin|admin/" . || true

Repository: CodingWithTushar/learningsaas

Length of output: 3662


🌐 Web query:

@clerk/nextjs createRouteMatcher route pattern forward slash api/admin

💡 Result:

As of July 22, 2026, the createRouteMatcher() helper in @clerk/nextjs is deprecated [1][2][3]. Clerk now recommends protecting resources directly (e.g., within Pages, Layouts, or API Route handlers) rather than relying on Middleware-based path matching [2][3]. If you are currently using createRouteMatcher and need to match the /api/admin route and its sub-paths, the following patterns are supported [4]: 1. To match /api/admin and everything under it: '/api/admin(.*)' [2] 2. Using the path segment syntax: '/api/admin/:path*' [1] Example usage (for legacy maintenance): import { createRouteMatcher } from '@clerk/nextjs/server' const isAdminRoute = createRouteMatcher(['/api/admin(.*)']) For new implementations or migrations, Clerk advises using resource-based authorization checks [2][3]. This involves invoking auth.protect() directly within your protected server-side resources instead of checking paths in middleware.ts [2][3]. For more details on migrating away from createRouteMatcher, refer to the official Clerk migration guide [2].

Citations:


🌐 Web query:

Clerk Next.js createRouteMatcher docs route patterns /api admin

💡 Result:

As of July 2026, the createRouteMatcher function is officially deprecated in the Clerk Next.js SDK [1][2]. It currently triggers a runtime deprecation warning, and Clerk recommends migrating toward resource-based authorization checks (such as using auth().protect() directly within your API routes or Server Components) instead of relying on centralized middleware path matching [1][2]. If you are currently using createRouteMatcher or maintaining existing implementations, the following patterns apply: Route Patterns and Syntax Clerk's route matchers support path patterns and regular expressions [3][4]. When defining paths, you can use the following conventions: - Use the :path* suffix for matching path-segment subtrees (e.g., /admin/:path* matches /admin, /admin/users, and /admin/settings/123) [1][5]. - The legacy (.*) suffix (e.g., /admin(.*)) is also supported but is generally discouraged in favor of the more precise :path* syntax [5]. - For API routes specifically, you would typically target them by prefixing the pattern, such as /api/admin/:path* [6]. Implementation Context In your middleware.ts (or proxy.ts), createRouteMatcher is typically used to determine which routes are public or protected [7][8]. Example of protecting an admin API route: import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'; const isAdminRoute = createRouteMatcher(['/api/admin/:path*']); export default clerkMiddleware(async (auth, req) => { if (isAdminRoute(req)) { await auth.protect; // Enforces authentication for matched routes } }); export const config = { matcher: [ // Ensure the middleware runs for API routes '/(api|trpc)(.)', // Standard Next.js internal/static file exclusion '/((?!_next|[^?]\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)', ], }; Critical Considerations - Middleware Limitations: Centralized middleware-based matching can diverge from how Next.js routes requests, potentially leaving some resources unintentionally exposed [2]. - Webhooks: Always ensure public API endpoints (like Stripe webhooks) are explicitly excluded from protection if they do not require user authentication [6]. - Migration: You are encouraged to review the official Clerk migration guide to transition your authentication logic to resource-based checks, which provide more granular security and better alignment with Next.js routing [1][2].

Citations:


Match the admin API path with a leading slash.

"api/admin(.*)" does not cover Next.js requests such as /api/admin/...; use a leading slash like "/api/admin(.*)" or the supported pattern "/api/admin/:path*".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@middleware.ts` around lines 18 - 22, Update the isAdminRoute matcher to use a
leading slash for the admin API pattern, ensuring requests under /api/admin/...
are matched while preserving the existing admin route patterns.

24-39: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== package files =="
git ls-files | rg '(^|/)(package\.json|pnpm-lock\.yaml|yarn\.lock|package-lock\.json)$' || true

echo "== middleware.ts locations =="
fd -a 'middleware\.(ts|js|mjs)$' . | sed 's#^\./##' | head -50

echo "== middleware.ts relevant file =="
if [ -f middleware.ts ]; then
  nl -ba middleware.ts | sed -n '1,120p'
fi

echo "== clerk nextjs dependency =="
if [ -f package.json ]; then
  node -e "const p=require('./package.json'); console.log(JSON.stringify({dependencies:p.dependencies?.['`@clerk/nextjs`'], devDependencies:p.devDependencies?.['`@clerk/nextjs`'], next:p.dependencies?.next, devDependenciesNext:p.devDependencies?.next}, null, 2))"
fi

echo "== search auth(), userId.role, clerk middleware docs local =="
rg -n "auth\\(|userId\\.|role|clerkMiddleware|next/dist/docs" -S . --glob '!node_modules/**' --glob '!dist/**' | head -200

Repository: CodingWithTushar/learningsaas

Length of output: 356


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== middleware.ts =="
awk '{printf "%6d\t%s\n", NR, $0}' middleware.ts | sed -n '1,140p'

echo "== package deps =="
node -e "const fs=require('fs'); const p=JSON.parse(fs.readFileSync('package.json','utf8')); console.log(JSON.stringify({next:p.dependencies?.next, clerk:p.dependencies?.['`@clerk/nextjs`'], devNext:p.devDependencies?.next, devClerk:p.devDependencies?.['`@clerk/nextjs`']}, null, 2))"

echo "== package-lock relevant package entries =="
node - <<'JS'
const fs=require('fs');
const p=JSON.parse(fs.readFileSync('package-lock.json','utf8'));
for (const [name,v] of Object.entries(p.packages || {}).filter(([n]) => n === '' || n === 'node_modules/@clerk/nextjs' || n === 'node_modules/next')) {
  console.log(v.version || name, v.dependencies && v.dependencies['node'], v.engines);
}
JS

echo "== target code checks =="
python3 - <<'PY'
from pathlib import Path
s=Path('middleware.ts').read_text()
print("auth destructure:", bool(("const { userId } = auth" in s) or "const { userId } = await auth" in s or "const { userId } = (await auth())" in s))
print("auth invoked:", bool("await auth({ " in s or "await auth()" in s or "auth({ " in s or "auth()" in s))
print("userId accesses:", [i+1 for i,l in enumerate(s.splitlines()) if 'userId' in l])
print("role access:", bool("userId.role" in s))
PY

echo "== local Next docs availability =="
find node_modules/next -type f \( -path '*docs*' -o -name '*.js' -o -name '*.d.ts' \) -maxdepth 4 2>/dev/null | head -50 || true

Repository: CodingWithTushar/learningsaas

Length of output: 3254


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== all middleware.ts =="
find . -name 'middleware.ts' -type f -print

echo "== TypeScript availability and Clerk auth typings from node_modules =="
if [ -d node_modules/@clerk/nextjs ]; then
  find node_modules/@clerk/nextjs -maxdepth 3 \( -name '*.d.ts' -o -name '*.d.mts' \) | xargs rg -n "declare function clerkMiddleware|clerkMiddleware\\(|auth\\??:|userId" | head -200 || true
  find node_modules/@clerk/nextjs -maxdepth 4 \( -name '*.d.ts' -o -name '*.d.mts' \) | xargs rg -n "createRouteMatcher|server" | head -100 || true
else
  echo "node_modules/@clerk/nextjs not present"
fi

if command -v tsc >/dev/null 2>&1; then
  echo "== TypeScript compiler availability =="
  tsc --version
  echo "== TypeScript check with minimal Clerk middleware type probe (no execution) =="
  mkdir -p /tmp/clerk-auth-probe
  cat >/tmp/clerk-auth-probe/probe.ts <<'TS'
import { clerkMiddleware, createRouteMatcher } from "@"__clerk_/nextjs/server";

export default clerkMiddleware((auth, req: Request) => {
  const { userId } = auth;
  if (userId && ("role" in userId && typeof userId.role === "string")) {
    void userId.role;
  }
});
TS
  tsc --noEmit --skipLibCheck /tmp/clerk-auth-probe/probe.ts 2>&1 | sed -n '1,160p'
else
  echo "tsc not available"
fi

Repository: CodingWithTushar/learningsaas

Length of output: 2478


🌐 Web query:

@clerk/nextjs middleware auth await auth() userId role clerkMiddleware user metadata role source

💡 Result:

To retrieve user metadata and roles within your Next.js middleware using Clerk, you must first ensure that the custom metadata is included in the session token [1][2][3]. ### Step 1: Customize Session Token By default, Clerk does not include publicMetadata in the session claims to keep the token size manageable [4]. To access this data in middleware without making additional API calls, you must customize the session token in the Clerk Dashboard: 1. Navigate to Sessions → Customize session token [1][5]. 2. Add a new claim (e.g., metadata) that maps to your user's public metadata [1]: json { "metadata": "{{user.public_metadata}}" } ### Step 2: Access in Middleware Once customized, you can access this data via sessionClaims inside the clerkMiddleware callback [1][6]. You must await the auth() function to retrieve the current authentication context [1][7][6]. typescript // middleware.ts import { clerkMiddleware, createRouteMatcher } from '`@clerk/nextjs/server`' import { NextResponse } from 'next/server' const isAdminRoute = createRouteMatcher(['/admin(.*)']) export default clerkMiddleware(async (auth, req) => { // Await the auth() helper to get context const { sessionClaims } = await auth() // Safely access your custom metadata claim const role = sessionClaims?.metadata?.role // Example: Protect routes based on role if (isAdminRoute(req) && role !== 'admin') { return NextResponse.redirect(new URL('/', req.url)) } }) export const config = { matcher: [ '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)', '/(api|trpc)(.*)', ], } ### Key Considerations * Await auth(): Always await the auth() function when calling it inside middleware or server-side components to resolve the promise [7]. * Metadata vs. Session Claims: Using sessionClaims is highly performant as it avoids extra network requests [1][2][3]. If you do not wish to add the claim to the token, you would have to fetch the user object via the Clerk API (clerkClient.users.getUser), which is significantly slower and subject to rate limits [4][1]. * Security: publicMetadata can be read on the client but only modified on the server [2][6]. Always perform critical authorization checks on the server side [8][1].

Citations:


🌐 Web query:

Clerk docs role-based access control middleware has() auth() userId

💡 Result:

In Clerk, role-based access control (RBAC) and general authorization are primarily handled using the auth helper, which returns an Auth object containing the current user's authentication and authorization state [1][2]. Key Components: 1. auth: This is the core server-side helper used to access user information and authorization tools [2][3]. When awaited, it returns an object containing the current userId, isAuthenticated status, and the has helper [4][1]. 2. userId: This property on the returned Auth object provides the ID of the currently signed-in user, which should be used to scope data or perform lookups [1][3]. 3. has: This is the recommended helper for performing authorization checks [4][5]. It accepts an object specifying the access control type—such as { role: '...' }, { permission: '...' }, { feature: '...' }, or { plan: '...' }—and returns a boolean value [4][1][5]. It is flexible and allows you to implement custom logic (e.g., returning a 403 error) when a check fails [4][5]. 4. auth.protect: For more concise route protection, you can use the protect method directly on the auth object [2][6]. It can enforce authorization by accepting the same parameters as has (e.g., auth.protect({ role: 'admin' })) [2][6]. If the user does not meet the requirements, it can automatically trigger a redirect or error [2]. Best Practices: - Authorization vs. Authentication: Use auth.userId to confirm a user is signed in (authenticated) and the has helper or auth.protect to verify their specific access rights (authorized) [4][2]. - Granularity: Clerk documentation recommends permission-based authorization over role-based authorization for greater flexibility and security [5]. - Location: While you can use auth.protect in middleware for fast-path enforcement, it is recommended to perform detailed authorization checks as close to the data source or business logic as possible (e.g., in Route Handlers or Server Actions) to ensure defense in depth [7][6]. - Limitations: When using has on the server side, note that it works with custom permissions; system permissions are not included in session token claims, so you should check for specific roles instead when dealing with system-level access [5].

Citations:


Invoke Clerk middleware auth and use Clerk authorization checks.

clerkMiddleware receives an async auth helper; const { userId } = auth; leaves authentication unread. Await auth() before checking login state, and replace userId.role with Clerk’s supported authorization flow (auth.has({ role: "admin" }) or claims/token-based role metadata). As written, protected admin routes can bypass auth checks or redirect users based on an invalid string property.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@middleware.ts` around lines 24 - 39, Update the clerkMiddleware callback to
await the provided auth helper before reading authentication state, then use the
returned auth context for the unauthenticated check. Replace the unsupported
userId.role comparison in the isAdminRoute branch with Clerk’s authorization
check, such as auth.has({ role: "admin" }), while preserving the existing
redirects.

47-64: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Return 401 for API requests before redirecting pages.

For an unauthenticated non-public API request, Line 50 redirects first, so Lines 56-63 never run. /api/upload-video therefore returns a sign-in redirect instead of the declared JSON 401 response. Exclude API requests from the redirect condition or handle isApiReq first.

Proposed fix
-    if (!isPublicRoutes(req) && !isPublicApiRoutes(req)) {
+    if (!isApiReq && !isPublicRoutes(req)) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  //If the User is not LoggedIn
  if (!userId) {
    //ProtectedRoutes user can not access them without LoggedIn
    if (!isApiReq && !isPublicRoutes(req)) {
      const signinUrl = new URL("/sign-in", req.url);
      signinUrl.searchParams.set("redirect_url", currentUrl.pathname);
      return NextResponse.redirect(signinUrl);
    }

    if (isApiReq && !isPublicApiRoutes(req)) {
      return NextResponse.json(
        {
          error: "UnAuthorized",
          message: "You must be logged in",
        },
        { status: 401 },
      );
    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@middleware.ts` around lines 47 - 64, Update the unauthenticated request
handling around isApiReq so non-public API requests reach the existing JSON 401
response before any sign-in redirect. Exclude API requests from the page
redirect condition, while preserving redirects for unauthenticated non-public
page routes and allowing public routes unchanged.
utils/helpers.ts (2)

7-8: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Fix the unawaited requireUser() caller.

app/api/upload-image/route.ts:9 calls this helper without await. Since authentication now suspends at Line 8, that handler starts the Cloudinary upload before authorization succeeds or rejects; the rejection also bypasses its try/catch. Change that call to await requireUser() and audit other callers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@utils/helpers.ts` around lines 7 - 8, Update callers of requireUser,
especially the upload-image route handler, to await the helper before starting
protected work so authentication completes and failures remain inside the
handler’s existing try/catch. Audit all other requireUser call sites and add
await wherever the returned promise is currently used without awaiting.

10-10: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Remove user-ID logging.

This writes a stable user identifier to server logs on every request without an operational need.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@utils/helpers.ts` at line 10, Remove the console.log call that logs userId
from the request handling flow, leaving the surrounding behavior unchanged.

@CodingWithTushar
CodingWithTushar merged commit eb4771e into main Jul 22, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant