feat: add protected application shell - #2
Conversation
📝 WalkthroughWalkthroughPR tích hợp xác thực Clerk theo hướng fail-closed. PR thêm các route xác thực, route ứng dụng được bảo vệ, application shell responsive, trạng thái lỗi và kiểm thử unit, Playwright, accessibility. ChangesXác thực Clerk và bảo vệ route
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/app/layout.tsx (1)
40-69: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winĐọc cấu hình Clerk tại thời điểm chạy.
RootLayouthiện đồng bộ và không khai báodynamic = "force-dynamic". Nếu Next.js render tĩnh layout này, nhánh có/không cóClerkProvidersẽ bị cố định theo biến môi trường tại build. Đặt môi trường production vàoNEXT_PUBLIC_CLERK_PUBLISHABLE_KEYvàCLERK_SECRET_KEYtrước build, hoặc thêmforce-dynamicđể quyết định cấu hình tại mỗi request.Kiểm tra prop sign-out.
afterSignOutUrlkhông còn được khuyến nghị và có thể đã deprecated trong@clerk/nextjs7.6.4. Dùng biến môi trường redirects của Clerk hoặc config redirect trong xử lý sign-out thay vì truyền prop vàoClerkProvider.🤖 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 `@src/app/layout.tsx` around lines 40 - 69, Update RootLayout to force request-time rendering so Clerk configuration is evaluated per request rather than fixed during build, and remove afterSignOutUrl from ClerkProvider because it is deprecated; configure sign-out redirection through Clerk environment settings or the existing sign-out handling instead.
🧹 Nitpick comments (6)
src/components/layout/auth-page.tsx (1)
46-52: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueBỏ một trong hai nhãn trùng lặp.
Phần tử có cả
aria-label="Loading authentication form"và văn bảnsr-onlycùng nội dung.aria-labelghi đè nội dung con, nên văn bảnsr-onlylà dư thừa. Hãy giữ một nguồn nhãn duy nhất.♻️ Đề xuất thay đổi
<div role="status" - aria-label="Loading authentication form" className="h-[31rem] w-full max-w-[25rem] animate-pulse rounded-xl border border-border bg-card motion-reduce:animate-none" > <span className="sr-only">Loading authentication form</span>🤖 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 `@src/components/layout/auth-page.tsx` around lines 46 - 52, Remove one of the duplicate accessible labels from the loading element in the authentication page: either remove aria-label or the matching sr-only span, leaving exactly one accessible name for the status container.src/app/(protected)/app/page.tsx (1)
26-45: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueCân nhắc bỏ kiểm tra phiên trùng lặp trong trang.
ProtectedAppLayoutđã kiểm tra cấu hình và phiên trước khi render children. Trang lặp lại toàn bộ chuỗi kiểm tra và thêm một lần gọigetClerkSessionState()cho mỗi request. Nếu bạn giữ lớp kiểm tra này có chủ ý, hãy thêm chú thích ngắn nêu rõ đây là phòng vệ theo chiều sâu.🤖 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 `@src/app/`(protected)/app/page.tsx around lines 26 - 45, Remove the duplicated configuration and session checks from the page around getClerkConfiguration and getClerkSessionState, relying on ProtectedAppLayout to validate access before rendering children. Preserve only page-specific rendering logic; if the checks must remain, add a brief comment identifying them as defense-in-depth.src/components/layout/app-shell.tsx (1)
45-52: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
aria-current="page"được gán cứng cho liên kết Overview.Hiện tại shell chỉ có một mục điều hướng, nên giá trị này đúng. Khi bạn thêm route con dưới
/app, liên kết Overview sẽ tiếp tục báo là trang hiện tại trên mọi route. Hãy tính giá trị từ pathname khi thêm mục điều hướng thứ hai.🤖 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 `@src/components/layout/app-shell.tsx` around lines 45 - 52, Update the Overview Link in the app-shell navigation to derive aria-current from the current pathname instead of hard-coding "page". Use the existing pathname-routing mechanism, marking it current only for APP_ROUTE while leaving it unset on child routes and preserving the current single-item behavior.src/proxy.ts (1)
13-51: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueSửa thứ tự tiêu đề trong trang 503.
Thẻ
<p>BridgeWorks</p>đứng trước<h1>. Nội dung thương hiệu nên nằm trong landmark hoặc sau tiêu đề chính để trình đọc màn hình đọc tiêu đề trang trước. Phần còn lại của phản hồi (no-store, CSP,noindex) là chính xác.🤖 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 `@src/proxy.ts` around lines 13 - 51, Update clerkUnavailableResponse so the main heading is encountered before the BridgeWorks branding paragraph, placing the branding after the h1 or within an appropriate landmark. Preserve the existing 503 response content, no-store cache policy, CSP, and noindex headers.src/app/(auth)/sign-in/[[...sign-in]]/page.tsx (1)
31-65: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winHai trang xác thực lặp cùng một cấu trúc phân nhánh. Mỗi tệp lặp lại
AuthPagebốn lần với cùngtitlevàdescription, và cả hai tệp dùng chung trình tự kiểm tra cấu hình rồi kiểm tra phiên. Nguyên nhân gốc là thiếu một thành phần dùng chung cho luồng xác thực.
src/app/(auth)/sign-in/[[...sign-in]]/page.tsx#L31-L65: táchtitlevàdescriptionthành hằng số, bọcAuthPagemột lần, và chỉ chọn nội dung con theo trạng thái.src/app/(auth)/sign-up/[[...sign-up]]/page.tsx#L31-L65: áp dụng cùng cấu trúc và dùng lại helper chung cho phần kiểm tra cấu hình và phiên.🤖 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 `@src/app/`(auth)/sign-in/[[...sign-in]]/page.tsx around lines 31 - 65, The authentication pages duplicate the same AuthPage branching and configuration/session checks. In src/app/(auth)/sign-in/[[...sign-in]]/page.tsx lines 31-65, extract shared title and description constants, render AuthPage once, and select only the AuthState child by status; in src/app/(auth)/sign-up/[[...sign-up]]/page.tsx lines 31-65, apply the same structure and reuse a shared helper for configuration and session checks. Preserve the existing redirects and state-specific AuthState values.src/app/(protected)/app/error.tsx (1)
14-14: 🧹 Nitpick | 🔵 TrivialCân nhắc ghi log
error.digestđể truy vết.Component nhận
errornhưng không dùng. Giao diện đúng khi không hiển thị chi tiết lỗi cho người dùng. Để hỗ trợ vận hành, hãy gửierror.digesttới hệ thống giám sát trong mộtuseEffect. Việc này giúp đối chiếu lỗi phía trình duyệt với log máy chủ.🤖 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 `@src/app/`(protected)/app/error.tsx at line 14, Trong component AppError, nhận và sử dụng prop error cùng reset, rồi thêm useEffect để gửi error.digest tới hệ thống giám sát khi lỗi thay đổi. Giữ nguyên giao diện hiện tại, không hiển thị chi tiết lỗi cho người dùng.
🤖 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 `@package.json`:
- Line 11: Remove the --passWithNoTests flag from the package.json test script
while preserving the unit project selection, so Vitest fails when the expected
src/**/*.test.{ts,tsx} tests are not discovered.
In `@src/app/`(protected)/app/layout.tsx:
- Around line 53-56: Replace the signed-out handling in
src/app/(protected)/app/layout.tsx#L53-L56 with next/navigation
redirect(SIGN_IN_ROUTE), ensuring the Server Component returns the supported
redirect behavior rather than session.redirectToSignIn(). Apply the same change
to the signed-out branch in src/app/(protected)/app/page.tsx#L37-L39; both sites
should redirect to SIGN_IN_ROUTE.
In `@src/proxy.ts`:
- Around line 61-81: Update the authentication flow inside the configuredProxy
clerkMiddleware callback to use Clerk v7’s App Router auth API rather than
reading session.isAuthenticated from auth(). Protect the request with
auth.protect() and preserve redirectToSignIn with the request URL for
unauthorized access; keep the pathname guard and successful NextResponse.next()
behavior unchanged.
In `@tests/example.spec.ts`:
- Around line 15-57: Update the Playwright webServer configuration used by
“renders deterministic secretless sign-in and sign-up states” and “fails
closed...” so the E2E server explicitly runs with CLERK_SECRET_KEY absent and
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY set to the placeholder value. Prevent
inherited .env.local credentials from affecting these assertions while leaving
the expected missing-configuration and 503 behavior unchanged.
---
Outside diff comments:
In `@src/app/layout.tsx`:
- Around line 40-69: Update RootLayout to force request-time rendering so Clerk
configuration is evaluated per request rather than fixed during build, and
remove afterSignOutUrl from ClerkProvider because it is deprecated; configure
sign-out redirection through Clerk environment settings or the existing sign-out
handling instead.
---
Nitpick comments:
In `@src/app/`(auth)/sign-in/[[...sign-in]]/page.tsx:
- Around line 31-65: The authentication pages duplicate the same AuthPage
branching and configuration/session checks. In
src/app/(auth)/sign-in/[[...sign-in]]/page.tsx lines 31-65, extract shared title
and description constants, render AuthPage once, and select only the AuthState
child by status; in src/app/(auth)/sign-up/[[...sign-up]]/page.tsx lines 31-65,
apply the same structure and reuse a shared helper for configuration and session
checks. Preserve the existing redirects and state-specific AuthState values.
In `@src/app/`(protected)/app/error.tsx:
- Line 14: Trong component AppError, nhận và sử dụng prop error cùng reset, rồi
thêm useEffect để gửi error.digest tới hệ thống giám sát khi lỗi thay đổi. Giữ
nguyên giao diện hiện tại, không hiển thị chi tiết lỗi cho người dùng.
In `@src/app/`(protected)/app/page.tsx:
- Around line 26-45: Remove the duplicated configuration and session checks from
the page around getClerkConfiguration and getClerkSessionState, relying on
ProtectedAppLayout to validate access before rendering children. Preserve only
page-specific rendering logic; if the checks must remain, add a brief comment
identifying them as defense-in-depth.
In `@src/components/layout/app-shell.tsx`:
- Around line 45-52: Update the Overview Link in the app-shell navigation to
derive aria-current from the current pathname instead of hard-coding "page". Use
the existing pathname-routing mechanism, marking it current only for APP_ROUTE
while leaving it unset on child routes and preserving the current single-item
behavior.
In `@src/components/layout/auth-page.tsx`:
- Around line 46-52: Remove one of the duplicate accessible labels from the
loading element in the authentication page: either remove aria-label or the
matching sr-only span, leaving exactly one accessible name for the status
container.
In `@src/proxy.ts`:
- Around line 13-51: Update clerkUnavailableResponse so the main heading is
encountered before the BridgeWorks branding paragraph, placing the branding
after the h1 or within an appropriate landmark. Preserve the existing 503
response content, no-store cache policy, CSP, and noindex headers.
🪄 Autofix
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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 6e18ffbc-9f13-4a62-afc6-739ab27857a7
📒 Files selected for processing (29)
.github/workflows/playwright.ymlREADME.mdpackage.jsonsrc/app/(auth)/sign-in/[[...sign-in]]/page.tsxsrc/app/(auth)/sign-up/[[...sign-up]]/page.tsxsrc/app/(protected)/app/error.tsxsrc/app/(protected)/app/layout.tsxsrc/app/(protected)/app/loading.tsxsrc/app/(protected)/app/page.tsxsrc/app/globals.csssrc/app/layout.tsxsrc/app/page.tsxsrc/components/layout/app-shell.stories.tsxsrc/components/layout/app-shell.test.tsxsrc/components/layout/app-shell.tsxsrc/components/layout/auth-page.tsxsrc/components/layout/auth-state.stories.tsxsrc/components/layout/auth-state.tsxsrc/components/layout/mobile-app-navigation.tsxsrc/lib/auth-routes.test.tssrc/lib/auth-routes.tssrc/lib/auth-session.server.tssrc/lib/clerk-config.server.tssrc/lib/clerk-config.test.tssrc/lib/clerk-config.tssrc/proxy.tstests/example.spec.tsvitest.config.tsvitest.setup.ts
📜 Review details
🧰 Additional context used
🪛 React Doctor (0.9.3)
src/lib/clerk-config.test.ts
[error] 13-13: This hardcoded secret is a security vulnerability: it ships to the browser where anyone can read it.
Move secrets to server-only code. In Next.js, only NEXT_PUBLIC_* env vars are exposed to the browser, and they must not contain secrets
(no-secrets-in-client-code)
🔇 Additional comments (19)
vitest.config.ts (1)
1-45: LGTM!vitest.setup.ts (1)
1-33: LGTM!tests/example.spec.ts (1)
4-13: LGTM!Also applies to: 59-68
.github/workflows/playwright.yml (1)
60-60: LGTM!src/app/page.tsx (1)
5-6: LGTM!Also applies to: 15-20, 38-49
src/lib/clerk-config.ts (1)
1-162: LGTM!src/lib/clerk-config.server.ts (1)
1-17: LGTM!src/lib/auth-session.server.ts (1)
1-27: LGTM!src/lib/clerk-config.test.ts (1)
1-109: LGTM!README.md (1)
38-38: LGTM!Also applies to: 47-72, 74-98, 106-140, 155-155, 168-171
src/components/layout/auth-state.stories.tsx (1)
1-37: LGTM!src/app/(protected)/app/loading.tsx (1)
1-17: LGTM!src/components/layout/mobile-app-navigation.tsx (1)
1-90: LGTM!src/app/globals.css (1)
132-141: LGTM!src/components/layout/app-shell.test.tsx (1)
1-70: LGTM!src/components/layout/app-shell.stories.tsx (1)
1-92: LGTM!src/lib/auth-routes.ts (1)
1-14: LGTM!src/lib/auth-routes.test.ts (1)
1-24: LGTM!src/components/layout/auth-state.tsx (1)
60-95: LGTM!
| "lint": "eslint", | ||
| "typecheck": "tsc --noEmit", | ||
| "test": "vitest run --passWithNoTests", | ||
| "test": "vitest run --project=unit --passWithNoTests", |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Không che giấu lỗi không phát hiện kiểm thử.
Repository đã có unit test trong src/. --passWithNoTests làm bước CI thành công nếu glob src/**/*.test.{ts,tsx} bị sai hoặc không còn tìm thấy kiểm thử. Hãy để Vitest thất bại trong trường hợp đó.
Đề xuất sửa
- "test": "vitest run --project=unit --passWithNoTests",
+ "test": "vitest run --project=unit",📝 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.
| "test": "vitest run --project=unit --passWithNoTests", | |
| "test": "vitest run --project=unit", |
🤖 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 `@package.json` at line 11, Remove the --passWithNoTests flag from the
package.json test script while preserving the unit project selection, so Vitest
fails when the expected src/**/*.test.{ts,tsx} tests are not discovered.
| const session = await getClerkSessionState(); | ||
| if (session.status === "signed-out") { | ||
| return session.redirectToSignIn({ returnBackUrl: APP_ROUTE }); | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Mô tả: Kiểm tra hợp đồng của getClerkSessionState và redirectToSignIn.
fd -t f 'auth-session' src | xargs -r cat -nRepository: DoMinhHHung/BridgeWorksApps
Length of output: 921
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Relevant files:"
fd -t f 'layout.tsx|page.tsx' 'src/app' | sort
echo
echo "layout.tsx:"
cat -n 'src/app/(protected)/app/layout.tsx'
echo
echo "page.tsx:"
cat -n 'src/app/(protected)/app/page.tsx'
echo
echo "All redirectToSignIn return usages:"
rg -n "redirectToSignIn|signed-out" 'src/app'Repository: DoMinhHHung/BridgeWorksApps
Length of output: 7455
🌐 Web query:
Clerk Next.js auth redirectToSignIn Server Components Response middleware Route Handler
💡 Result:
In Clerk's Next.js SDK, the recommended approach for handling authentication and redirects varies based on the context (Server Components, Middleware, or Route Handlers). Server Components In Server Components, use the auth helper from @clerk/nextjs/server to access authentication state and redirection methods [1][2]. - Use auth.redirectToSignIn to explicitly redirect unauthenticated users to your sign-in page [3][1]. - Alternatively, use await auth.protect, which automatically redirects unauthenticated users to the sign-in page for document requests (pages) [3][4]. Middleware In your middleware.ts, use the clerkMiddleware function [5][6]. - You can protect routes by using auth.protect inside the middleware callback [4][6]. - If you need custom redirect logic (e.g., conditional redirects based on complex logic), you can manually check the user ID and return a NextResponse.redirect [7]. Route Handlers and Server Actions For non-document requests like Route Handlers or Server Actions, auth.protect does not redirect to the sign-in page; instead, it throws a 401 error or a 404/unauthorized error, as these are typically API or background requests [3][4]. - To handle unauthenticated states in these contexts, use auth to check isAuthenticated and implement your own response (e.g., return a JSON response with a 401 status) [3][2][8]. Key Considerations - clerkMiddleware: This is required to be configured for auth and related helpers to function [5][1]. - Async Requirements: Since version 6, auth.protect is an asynchronous method and must be awaited [7]. - Dynamic Rendering: Using auth in a Server Component will opt that route into dynamic rendering [9].
Citations:
- 1: https://clerk.com/docs/reference/nextjs/app-router/auth
- 2: https://clerk.com/docs/nextjs/guides/users/reading
- 3: https://clerk.com/docs/nextjs/guides/secure/protect-content
- 4: feat(nextjs): Introduce clerkMiddleware clerk/javascript#2404
- 5: https://clerk.com/docs/reference/nextjs/clerk-middleware
- 6: https://clerk.com/nextjs-authentication
- 7: [@clerk/nextjs v7]: auth.protect() in Next.js 16 proxy redirects to current URL instead of sign-in page clerk/javascript#8302
- 8: https://clerk.com/docs/reference/nextjs/app-router/server-actions
- 9: https://clerk.com/docs/guides/development/rendering-modes
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Next/Clerk package versions:"
if [ -f package.json ]; then
node - <<'JS'
const fs = require('fs');
const pkg = JSON.parse(fs.readFileSync('package.json','utf8'));
console.log(JSON.stringify({
dependencies: {
next: pkg.dependencies?.next,
"`@clerk/nextjs`": pkg.dependencies?.["`@clerk/nextjs`"],
},
devDependencies: {
"`@clerk/nextjs`": pkg.devDependencies?.["`@clerk/nextjs`"],
}
}, null, 2));
JS
fi
echo
echo "Package lock snippets:"
fd -t f 'package-lock.json|pnpm-lock.yaml|yarn.lock|bun.lockb' . -d 1 | while read -r f; do
[ -f "$f" ] && { echo "--- $f"; rg -n '(`@clerk/nextjs`|next@|next:|next)?:?' "$f" | head -40; }
doneRepository: DoMinhHHung/BridgeWorksApps
Length of output: 1971
Không trả về redirectToSignIn() trực tiếp từ Server Component. getClerkSessionState() trả về đúng session.redirectToSignIn, và session.redirectToSignIn(...) ở đây trả về Response, không phải React node. Đổi cả hai nhánh signed-out thành redirect(SIGN_IN_ROUTE) của next/navigation, hoặc dùng ClerkServer API trả về dấu để render ClerkProvider.
src/app/(protected)/app/layout.tsx#L53-L56: thay bằngredirect(SIGN_IN_ROUTE)hoặc API Clerk Server Component hỗ trợ chuyển hướng.src/app/(protected)/app/page.tsx#L37-L39: áp dụng cùng cách xử lý cho nhánhsigned-out.
📍 Affects 2 files
src/app/(protected)/app/layout.tsx#L53-L56(this comment)src/app/(protected)/app/page.tsx#L37-L39
🤖 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 `@src/app/`(protected)/app/layout.tsx around lines 53 - 56, Replace the
signed-out handling in src/app/(protected)/app/layout.tsx#L53-L56 with
next/navigation redirect(SIGN_IN_ROUTE), ensuring the Server Component returns
the supported redirect behavior rather than session.redirectToSignIn(). Apply
the same change to the signed-out branch in
src/app/(protected)/app/page.tsx#L37-L39; both sites should redirect to
SIGN_IN_ROUTE.
| const configuredProxy = | ||
| clerkConfiguration.status === "configured" | ||
| ? clerkMiddleware( | ||
| async (auth, request) => { | ||
| if (!isProtectedAppPathname(request.nextUrl.pathname)) { | ||
| return NextResponse.next(); | ||
| } | ||
|
|
||
| const session = await auth(); | ||
| if (!session.isAuthenticated) { | ||
| return session.redirectToSignIn({ returnBackUrl: request.url }); | ||
| } | ||
|
|
||
| return NextResponse.next(); | ||
| }, | ||
| { | ||
| signInUrl: SIGN_IN_ROUTE, | ||
| signUpUrl: SIGN_UP_ROUTE, | ||
| }, | ||
| ) | ||
| : unavailableProxy; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
@clerk/nextjs v7 clerkMiddleware auth() isAuthenticated property
💡 Result:
In @clerk/nextjs v7, the auth() helper returns an object that includes the isAuthenticated property [1][2]. This property is a boolean that you can use to check if a user is signed in, effectively replacing the older !!userId pattern [3][2]. Key points regarding auth() in v7: - Asynchronous: You must await auth() to access its properties [3][4]. - Usage: It is designed for use in the App Router, specifically within Server Components, Route Handlers, and Server Actions [1][2]. - protect() vs isAuthenticated: While isAuthenticated allows you to handle unauthenticated states manually (e.g., returning a custom 401 response or redirecting) [2], the auth.protect() method is the recommended way to enforce protection automatically [1][5][6]. - clerkMiddleware(): clerkMiddleware() must be configured for auth() to function correctly [1][7], but auth() is the helper used to retrieve the authentication state, not clerkMiddleware() directly [7]. Example usage: import { auth } from '@clerk/nextjs/server' export default async function Page { const { isAuthenticated, userId } = await auth if (!isAuthenticated) { return
Citations:
- 1: https://clerk.com/docs/reference/nextjs/app-router/auth
- 2: https://clerk.com/docs/nextjs/guides/secure/protect-content
- 3: https://github.com/clerk/skills/blob/main/skills/frameworks/clerk-nextjs-patterns/SKILL.md
- 4: https://clerk.com/docs/guides/development/upgrading/upgrade-guides/nextjs-v6
- 5: https://clerk.com/docs/guides/development/upgrading/upgrade-guides/migrate-from-create-route-matcher
- 6: https://github.com/clerk/javascript/releases/tag/%40clerk/nextjs%407.5.14
- 7: https://clerk.com/docs/reference/nextjs/clerk-middleware
🌐 Web query:
@clerk/nextjs 7.6.4 ClerkMiddlewareRequestHandler authenticateRequest returnBackUrl
💡 Result:
In @clerk/nextjs version 7.6.4, the ClerkMiddlewareRequestHandler (the function passed to clerkMiddleware) provides an auth() helper [1][2]. This auth() helper includes the redirectToSignIn() method, which accepts an optional returnBackUrl parameter [2][3]. To use this within your middleware: 1. The auth() helper is returned by the middleware handler [2]. 2. You can call auth().redirectToSignIn({ returnBackUrl: 'your-url' }) [3]. When you invoke auth().redirectToSignIn({ returnBackUrl }), Clerk handles the redirection logic to the configured sign-in URL, appending the specified returnBackUrl so that the user can be redirected back to the desired page after authentication [2][4][3]. Regarding authenticateRequest: This is a low-level method found on the clerkClient object (accessible via await clerkClient() in Server Components or Route Handlers), not a direct parameter within the clerkMiddleware handler [5][6]. It is used to verify session tokens directly rather than managing redirect flows within middleware [5]. If you are looking to protect routes or manage redirects within clerkMiddleware, the auth().protect() or auth().redirectToSignIn() methods are the intended high-level patterns [4][3]. Note that if you use auth().protect(), it internally handles redirects and, in many cases, automatically manages the returnBackUrl to redirect the user back to the current page after they sign in [4][3]. If you encounter issues where the returnBackUrl is resolving incorrectly (e.g., to an empty string or current URL in specific environments), ensure your environment variables like NEXT_PUBLIC_CLERK_SIGN_IN_URL are correctly configured, as Clerk relies on these to resolve the destination [7].
Citations:
- 1: https://cdn.jsdelivr.net/npm/@clerk/nextjs@7.6.4/dist/types/server/clerkMiddleware.d.ts
- 2: https://cdn.jsdelivr.net/npm/@clerk/nextjs@7.6.4/dist/types/app-router/server/auth.d.ts
- 3: feat(nextjs): Introduce clerkMiddleware clerk/javascript#2404
- 4: v5: NextJS improvements clerk/javascript#2691
- 5: https://clerk.com/docs/guides/sessions/manual-jwt-verification
- 6: https://clerk.com/docs/reference/backend/overview
- 7: [@clerk/nextjs v7]: auth.protect() in Next.js 16 proxy redirects to current URL instead of sign-in page clerk/javascript#8302
🏁 Script executed:
#!/bin/bash
set -euo pipefail
tmp="$(mktemp -d)"
cd "$tmp"
npm pack `@clerk/nextjs`@7.6.4 --silent > /tmp/nextjs764_pkg.txt
tarball="$(cat /tmp/nextjs764_pkg.txt)"
tar -xzf "$tarball"
echo '--- auth.d.ts relevant ---'
grep -n -A 8 -B 2 'isAuthenticated' node_modules/@clerk/nextjs/dist/types/server/auth.d.ts || true
echo '--- auth runtime relevant ---'
grep -n -A 12 -B 5 'isAuthenticated' node_modules/@clerk/nextjs/dist/cjs/server/auth.js || true
echo '--- clerkMiddleware.d.ts relevant ---'
grep -n -A 12 -B 4 'clerkMiddleware' node_modules/@clerk/nextjs/dist/types/server/clerkMiddleware.d.ts || trueRepository: DoMinhHHung/BridgeWorksApps
Length of output: 694
🏁 Script executed:
#!/bin/bash
set -euo pipefail
tmp="$(mktemp -d)"
cd "$tmp"
npm pack `@clerk/nextjs`@7.6.4 --silent > /tmp/nextjs764_pkg.txt
tarball="$(cat /tmp/nextjs764_pkg.txt)"
mkdir pkg
tar -xzf "$tarball" -C pkg
echo '--- package structure ---'
find pkg -maxdepth 3 -type f | sort
echo '--- auth.d.ts relevant ---'
find pkg -name auth.d.ts -print | while read -r f; do
echo "FILE: $f"
grep -n -A 8 -B 2 'isAuthenticated' "$f" || true
done
echo '--- auth runtime relevant ---'
find pkg -path '*.js' | while read -r f; do
case "$f" in
*/auth.js|*/auth.cjs|*auth*runtime*|*getAuth*|*MiddlewareRequestHandler*)
echo "FILE: $f"
grep -n -A 12 -B 5 'isAuthenticated' "$f" || true
;;
esac
done
echo '--- clerkMiddleware type/runtime relevant ---'
find pkg -type f \( -name '*middleware*' -o -name '*Middleware*' -o -name '*proxy*' \) -path '*.d.ts' -path '*.js' -print | while read -r f; do
echo "FILE: $f"
grep -n -A 12 -B 5 'clerkMiddleware\|isAuthenticated\|NextResponse\.next' "$f" || true
doneRepository: DoMinhHHung/BridgeWorksApps
Length of output: 1815
🏁 Script executed:
#!/bin/bash
set -euo pipefail
tmp="$(mktemp -d)"
cd "$tmp"
npm pack `@clerk/nextjs`@7.6.4 --silent
tarball="$(cat /tmp/nextjs764_pkg.txt)"
mkdir pkg
tar -xzf "$tarball" -C pkg
echo '--- package files around auth.js ---'
find pkg -name auth.js -o -name auth.d.ts -o -name getAuth.d.ts -o -name clerkMiddleware.d.ts | sort
echo '--- app-router auth.d.ts ---'
file="$(find pkg -name auth.d.ts | head -n 1)"
if [ -n "${file:-}" ]; then
sed -n '1,220p' "$file"
fi
echo '--- app-router auth ES source ---'
file="$(find pkg -path '*/dist/esm/app-router/server/auth.js' | head -n 1)"
if [ -n "${file:-}" ]; then
sed -n '1,220p' "$file"
fi
echo '--- package files matching middleware/proxy ---'
find pkg -type f \( -iname '*middleware*' -o -iname '*proxy*' \) | sortRepository: DoMinhHHung/BridgeWorksApps
Length of output: 9996
🏁 Script executed:
#!/bin/bash
set -euo pipefail
tmp="$(mktemp -d)"
cd "$tmp"
npm pack `@clerk/nextjs`@7.6.4 --silent > /tmp/nextjs764_pkg.txt
tarball="$(cat /tmp/nextjs764_pkg.txt)"
mkdir pkg
tar -xzf "$tarball" -C pkg
echo '--- clerkMiddleware type relevant lines ---'
sed -n '1,220p' pkg/package/dist/types/server/clerkMiddleware.d.ts | grep -n -A 8 -B 8 'ClerkMiddlewareRequestHandler\|clerkMiddleware\|auth\|ClerkProxyResponse' || true
echo '--- app-router server auth.d.ts SessionAuthWithRedirect ---'
sed -n '1,80p' pkg/package/dist/types/app-router/server/auth.d.ts
echo '--- backend SessionAuthObject isAuthenticated in `@clerk/backend` ---'
npm pack `@clerk/backend`@1.25.4 --silent > /tmp/backend_pkg.txt
tarball2="$(cat /tmp/backend_pkg.txt)"
mkdir pkgbe
tar -xzf "$tarball2" -C pkgbe
find pkgbe -name '*auth*' -name '*.d.ts' | sort
while IFS= read -r f; do
echo "FILE: $f"
grep -n -A 8 -B 3 'SessionAuthObject\|isAuthenticated' "$f" || true
done < <(find pkgbe -name '*auth*' -name '*.d.ts' | sort)Repository: DoMinhHHung/BridgeWorksApps
Length of output: 7835
🏁 Script executed:
#!/bin/bash
set -euo pipefail
tmp="$(mktemp -d)"
cd "$tmp"
npm pack `@clerk/nextjs`@7.6.4 --silent > /tmp/nextjs764_pkg.txt
npm pack `@clerk/backend`@1.25.4 --silent > /tmp/backend_pkg.txt
tarball="$(cat /tmp/nextjs764_pkg.txt)"
tarball2="$(cat /tmp/backend_pkg.txt)"
mkdir pkg pkgbe
tar -xzf "$tarball" -C pkg
tar -xzf "$tarball2" -C pkgbe
echo '--- pkgbe auth object relevant files ---'
for f in pkgbe/package/dist/tokens/authObjects.d.ts pkgbe/package/dist/tokens/authStatus.d.ts pkgbe/package/dist/tokens/authenticateContext.d.ts; do
if [ -f "$f" ]; then
echo "### $f"
grep -n -A 12 -B 4 'isAuthenticated\|SignedInAuthObject\|SignedOutAuthObject\|SessionAuthObject'\* "$f" || true
fi
done
echo '--- pkg nextjs imports SessionAuthObject ---'
grep -R -n "SessionAuthObject" pkg/package/dist/types || trueRepository: DoMinhHHung/BridgeWorksApps
Length of output: 5476
🏁 Script executed:
#!/bin/bash
set -euo pipefail
tmp="$(mktemp -d)"
cd "$tmp"
npm pack `@clerk/backend`@1.25.4 --silent > /tmp/backend_pkg.txt
tarball="$(cat /tmp/backend_pkg.txt)"
mkdir pkg
tar -xzf "$tarball" -C pkg
echo '--- all SessionAuthObject definitions/references ---'
grep -R -n "SessionAuthObject" pkg/package/dist || true
echo '--- all isAuthenticated definitions/references ---'
grep -R -n "isAuthenticated" pkg/package/dist || trueRepository: DoMinhHHung/BridgeWorksApps
Length of output: 432
Sử dụng thuộc tính xác thực phù hợp với API auth() Clerk v7.
clerkMiddleware v7 truyền auth() là type App Router (auth.protect) chứ không truyền SignedInAuthObject | SignedOutAuthObject. Nếu auth() phải trả về auth object middleware, hãy xác định đúng type; còn nếu chỉ dùng redirectToSignIn, bỏ check session.isAuthenticated và gọi redirectToSignIn() khi auth.protect() lỗi/unauthorized.
🤖 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 `@src/proxy.ts` around lines 61 - 81, Update the authentication flow inside the
configuredProxy clerkMiddleware callback to use Clerk v7’s App Router auth API
rather than reading session.isAuthenticated from auth(). Protect the request
with auth.protect() and preserve redirectToSignIn with the request URL for
unauthorized access; keep the pathname guard and successful NextResponse.next()
behavior unchanged.
| test("renders deterministic secretless sign-in and sign-up states", async ({ | ||
| page, | ||
| }) => { | ||
| await page.goto("/sign-in"); | ||
| await expect( | ||
| page.getByRole("heading", { name: "Sign in to BridgeWorks", level: 1 }), | ||
| ).toBeVisible(); | ||
| await expect( | ||
| page.getByRole("heading", { | ||
| name: /Authentication (is not configured|setup is incomplete|configuration is invalid)/, | ||
| level: 2, | ||
| }), | ||
| ).toBeVisible(); | ||
|
|
||
| await page.goto("/sign-up"); | ||
| await expect( | ||
| page.getByRole("link", { name: "Explore the product foundation" }), | ||
| page.getByRole("heading", { | ||
| name: "Create your BridgeWorks account", | ||
| level: 1, | ||
| }), | ||
| ).toBeVisible(); | ||
| await expect( | ||
| page.getByRole("heading", { | ||
| name: /Authentication (is not configured|setup is incomplete|configuration is invalid)/, | ||
| level: 2, | ||
| }), | ||
| ).toBeVisible(); | ||
| }); | ||
|
|
||
| test("has no automatically detectable accessibility violations", async ({ | ||
| test("fails closed when a protected route has no usable Clerk configuration", async ({ | ||
| page, | ||
| }) => { | ||
| await page.goto("/"); | ||
| const response = await page.goto("/app"); | ||
|
|
||
| const results = await new AxeBuilder({ page }).analyze(); | ||
| expect(response?.status()).toBe(503); | ||
| await expect( | ||
| page.getByRole("heading", { | ||
| name: "Authentication is temporarily unavailable", | ||
| level: 1, | ||
| }), | ||
| ).toBeVisible(); | ||
| await expect(page.getByText(/sk_test|CLERK_SECRET_KEY/)).toHaveCount(0); | ||
| }); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Tìm cấu hình Playwright và lệnh khởi động E2E.
fd -HI '^playwright\.config\.' .
rg -n -C 6 '"test:e2e"|playwright|webServer|CLERK_SECRET_KEY|NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY' \
package.json .github tests \
$(fd -HI '^playwright\.config\.' .)
# Kỳ vọng: web server E2E xóa hoặc thay thế rõ ràng cả hai biến Clerk.Repository: DoMinhHHung/BridgeWorksApps
Length of output: 5990
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== env files =="
git ls-files | rg '(^|/)\.env|(^|/)app/|CLERK|CLERK_SECRET_KEY|NEXT_PUBLIC_CLERK|sign-in|sign-up|clerk|middleware' || true
echo
echo "== playwright config =="
cat -n playwright.config.ts
echo
echo "== example test =="
cat -n tests/example.spec.ts
echo
echo "== focused Clerk environment usage =="
rg -n -C 3 'CLERK_SECRET_KEY|NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY|Clerk|sign-in|sign-up|middleware' . \
-g '!node_modules' -g '!dist' -g '!build' -g '!playwright-report' -g '!test-results' || trueRepository: DoMinhHHung/BridgeWorksApps
Length of output: 40597
🌐 Web query:
Clerk Next.js ClerkProvider publishableKey required when server-side auth and middleware configured
💡 Result:
In Clerk for Next.js, you generally do not need to pass the publishableKey as a prop to the ClerkProvider component if it is correctly set in your environment variables [1][2]. The ClerkProvider and clerkMiddleware helpers are designed to automatically look for environment variables to initialize the SDK [2]. The standard approach is to store your keys in your project's .env or .env.local file using the key NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY [1][3]. Key points regarding the publishableKey: 1. Default Environment Loading: When you provide the key as an environment variable, the SDK automatically detects it. You can simplify your setup by omitting the publishableKey prop from the ClerkProvider component in your layout files [1][4]. 2. Middleware Configuration: Similarly, when using clerkMiddleware, the SDK resolves the necessary configuration (including the publishable key) from your environment variables [5]. While clerkMiddleware does support passing keys as arguments for advanced use cases (like dynamic multi-tenant setups), it is not required for standard configurations [5]. 3. Recommended Setup: The recommended way to ensure your environment is configured correctly is to use the Clerk CLI by running npx clerk@latest env pull [1][2]. This command automatically retrieves the correct keys from your Clerk Dashboard and saves them to your .env file with the required variable names [1][2]. If you are using TypeScript, modern versions of the Clerk SDK have been updated to treat the publishableKey prop as optional in ClerkProvider, reflecting that it will correctly fall back to environment variables at runtime [4].
Citations:
- 1: https://clerk.com/docs/nextjs/reference/components/clerk-provider
- 2: https://clerk.com/articles/add-clerk-authentication-to-a-next-js-app-with-the-clerk-cli
- 3: https://clerk.com/docs/getting-started/quickstart/pages-router
- 4: fix(react): make ClerkProvider publishableKey optional clerk/javascript#9314
- 5: https://clerk.com/docs/reference/nextjs/clerk-middleware
🌐 Web query:
Clerk Next.js configuration missing publishableKey CLERK_SECRET_KEY not set error
💡 Result:
The error "Missing publishableKey" or "CLERK_SECRET_KEY not set" typically occurs because the Clerk SDK cannot access your environment variables during the build process or at runtime [1][2][3]. ### Recommended Troubleshooting Steps 1. Verify Environment Variable Names Ensure your environment variables are named exactly as expected by Clerk [4][5]. * For the publishable key: Use NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY [4][5]. The NEXT_PUBLIC_ prefix is required for Next.js to expose this variable to the client-side [4]. * For the secret key: Use CLERK_SECRET_KEY [4][5]. This must never be exposed to the client [6]. 2. Check Deployment Platform Configuration If this error occurs on a hosting platform (e.g., Vercel, Netlify, AWS Amplify) but works locally, the platform may not be injecting the variables during the build phase [2][3]. * Ensure variables are set for all environments: Confirm they are added to your dashboard's production, preview, and development environment settings [3]. * Build-time injection: Some platforms require you to explicitly include environment variables in your build command if they are not picked up automatically [2]. For example, in some CI/CD pipelines, you may need to write the variables to a .env.production file during the build process [2]: bash env | grep -e NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY >> .env.production env | grep -e CLERK_SECRET_KEY >> .env.production npm run build 3. Use the Clerk CLI The most reliable way to set up these variables is using the Clerk CLI, which automatically pulls and configures them for your project [6][7]. * Run the following command to pull your keys directly into your .env.local file [7]: bash npx clerk@latest env pull * This command is idempotent and helps ensure the keys are correctly formatted and placed [7]. 4. Explicitly Pass Keys (If Needed) If standard environment variable loading still fails, you can explicitly pass the keys to the ClerkProvider in your layout file [1][3]. Note that this is generally a fallback and should not be necessary with a correct environment configuration [8]. jsx <ClerkProvider publishableKey={process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY} > {children} </ClerkProvider> ### Important Security Reminder Never commit your CLERK_SECRET_KEY to version control [6][7]. Always keep it in files ignored by git (e.g., .env.local) and ensure it is only available to server-side environments [4][6].
Citations:
- 1: https://stackoverflow.com/questions/76248386/clerk-fails-to-get-the-publishable-key-from-the-environment-variables-in-next-js
- 2: https://stackoverflow.com/questions/78974942/missing-publishable-key-in-aws-amplify-deployment-despite-setting-environment-va
- 3: https://community.vercel.com/t/vercel-not-injecting-clerk-publishable-key-at-build-time/16047
- 4: https://clerk.com/articles/authentication-for-serverless-and-edge-deployments-2
- 5: https://clerk.com/docs/guides/development/clerk-environment-variables
- 6: https://clerk.com/docs/nextjs/getting-started/quickstart
- 7: https://clerk.com/articles/add-clerk-authentication-to-a-next-js-app-with-the-clerk-cli
- 8: redirectToSignIn not respecting publishableKey passed to authMiddleware clerk/javascript#1442
Cụ thể hóa môi trường Clerk cho E2E.
Các kiểm thử này mong đợi trạng thái missing trên /sign-in, /sign-up, và 503 trên /app. Hiện cấu hình Playwright chỉ chạy pnpm dev, trong khi .env.example chứa các khóa placeholder hợp lý; nếu CI/giới thiệu người khác có .env.local thay thế với khóa hợp lệ, assertions sẽ fail. Trong cấu hình Playwright, bỏ qua CLERK_SECRET_KEY và truyền NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY placeholder vào server E2E, hoặc khởi động server với các biến này tường minh.
🤖 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 `@tests/example.spec.ts` around lines 15 - 57, Update the Playwright webServer
configuration used by “renders deterministic secretless sign-in and sign-up
states” and “fails closed...” so the E2E server explicitly runs with
CLERK_SECRET_KEY absent and NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY set to the
placeholder value. Prevent inherited .env.local credentials from affecting these
assertions while leaving the expected missing-configuration and 503 behavior
unchanged.
Objective
Add the first production-oriented authenticated application boundary for BridgeWorks Apps without implementing business features or inventing backend APIs.
Final hardening corrections
server-onlyboundary tosrc/lib/clerk-config.server.tsandsrc/lib/auth-session.server.ts;sk_test_/sk_live_environment prefixes and a non-empty opaque payload;pk_test_/pk_live_prefix and base64-encoded Frontend API value with its trailing$delimiter;No real credentials were added, logged, rendered, snapshotted, or committed.
What changed
configured,missing,placeholder, ormalformed;/app/**policy;/app;Overview → /app;Route policy
//sign-in/[[...sign-in]]/sign-up/[[...sign-up]]/appand/app/**/api/**The proxy is an early redirect/fail-closed boundary. Protected server resources repeat the session check because a layout check alone is not sufficient for every client navigation or future resource.
Server-only build verification
A temporary routable Client Component imported both server modules and intentionally ran the production build.
30969255622;92189821782;pnpm buildfailed;src/lib/auth-session.server.ts—server-onlycannot be imported from a Client Component module;src/lib/clerk-config.server.ts—server-onlycannot be imported from a Client Component module.The temporary route was removed before final validation and is not part of the final diff.
Backend contract verification
Backend
mainconfirms:Authorization: Bearer <Clerk session token>;X-Request-Idis preserved;{code,message,request_id,details}.This PR does not call the backend and does not add an API client.
Responsive and accessibility behavior
header/nav/main,aria-current, focus trap, Escape close, focus return, visible focus, reduced-motion support, and no hover-only controls.Final validation
Final head:
3c1f3e9ab10c57ed504de83a18a7ba7d0fc72fc9GitHub Actions run
30969358814, job92190135160, completed successfully on the final clean branch state:pnpm lint— passed;pnpm typecheck— passed;pnpm test— passed, 21 tests;pnpm build— passed;pnpm build-storybook— passed;pnpm test:storybook— passed;pnpm test:e2e— passed, including the configured Chromium, Firefox, and WebKit browser projects.Authenticated Clerk E2E limitation
Authenticated Clerk E2E is not claimed. CI has no dedicated Clerk test instance or test credentials, so it validates public routes, pure route/configuration policy, presentational shell behavior, deterministic unavailable states, fail-closed protected access, browser checks, and accessibility without weakening production authentication.
Manual authenticated smoke status
Pending — not executed. The README now contains the checklist for signed-out redirect, real Clerk rendering and sign-in, return to
/app, session persistence,UserButton, sign-out and renewed protection,.env.localignore status, and browser secret exposure checks.Scope exclusions
No jobs, talent, organizations workflow, marketplace, applications, billing, messaging, fake analytics, backend changes, APISIX changes, custom authentication, or production deployment.
Status
3c1f3e9ab10c57ed504de83a18a7ba7d0fc72fc9;Summary by CodeRabbit
Tính năng mới
Cải tiến
Kiểm thử