| Duration | ~50 min in the lesson + ~20 min homework |
| Prerequisites | Checkpoint lesson-4.5. |
| Checkpoint | lesson-4.6 |
What you will have
Your site has a private area at /admin. Signed out, it sends you to /admin/login. Signed in as the owner, it opens. Nobody else can create an account.
Video
The video for this lesson is not recorded yet.
Prompts used in this lesson
Part 5 — Build the login and the protected area
Purpose: a private admin area that only the owner can open. Section 5 will build the CRM inside it; this lesson builds only the door. Context: checkpoint lesson-4.5. Supabase is connected; NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY are set. I created one user (the owner) in the Supabase dashboard and turned off "Allow new users to sign up". Check package.json for our Next.js version. Do not open or print .env.local. Done looks like: - Supabase Auth set up the way Supabase's current "server-side auth for Next.js" guide describes: the @supabase/ssr package, a browser client, a server client, and the request-interception file (proxy.ts on Next.js 16 or later, middleware.ts on earlier versions) that refreshes the session. - /admin/login: an accessible email and password form using sign-in with password. Wrong credentials show one generic message that does not reveal whether the email exists. No sign-up link, no sign-up code anywhere. - /admin: a placeholder page that says "Admin — signed in as <email>" with a Sign out button. Sign out ends the session and returns to /admin/login. - Protection on the server, in two layers: (1) the proxy redirects any signed-out request for /admin or below to /admin/login, except /admin/login itself; (2) one shared server helper, used by every admin page and admin endpoint, that verifies the session with getClaims() — never by trusting getSession() — and redirects (pages) or returns 401 (endpoints). - One small protected endpoint, /api/admin/whoami, that returns the signed-in email or 401. It exists to prove endpoint protection and will be reused by tests. - A signed-in visit to /admin/login goes straight to /admin. - Admin pages are excluded from search engines and from the sitemap, and are never cached for other visitors. - The public site, /api/leads and its spam protection behave exactly as before. - CLAUDE.md gets a rule: "Every page under /admin and every endpoint under /api/admin must call the shared auth helper first." docs/architecture.md gets an "Admin access" section. Constraints: follow the current Supabase guide rather than memory, and tell me which file name (proxy or middleware) you used and why. The secret key is not needed for sign-in; do not use it here. Do not print or ask for the owner's password; I will sign in myself in the browser. Before you report: with the dev server running and no session, request /admin and /api/admin/whoami and show me the status codes and where /admin redirects. Run the existing test scripts for the lead endpoint, and the production build.
Part 7 — A second person cannot register
Purpose: prove that nobody can register a second account, even by calling Supabase Auth directly with our public key. Done looks like: a script `npm run test:signup-closed` that loads the env file, uses only the public URL and publishable key (never the secret key), attempts to sign up with a random address at example.com and a random password, and prints the result. PASS if Supabase refuses the sign-up; FAIL if a user or session is returned. Print the error message Supabase returned, nothing else. Constraints: do not print any key. Do not create users any other way. Before you report: run it and show the output.
Do along
Pause the video where a step says so and do it on your own project.
- Pause after Part 4. In Supabase, create your owner user with a strong password from a password manager.
- Turn off "Allow new users to sign up".
- Pause after Part 5. Give Claude the prompt from Part 5 unchanged.
- Pause after Part 6. In a private window: open
/adminsigned out, then/api/admin/whoami; try a wrong password; sign in; reload; sign out. - Pause after Part 7. Give Claude the prompt from Part 7 and run
npm run test:signup-closed. - Commit, push, and repeat step 4 on your live domain.
Check your work
- Signed out, open
https://<your-domain>/admin. Expected: you arrive at/admin/loginwithout seeing admin content. - Signed out, open
/api/admin/whoami. Expected: status 401. - Sign in with the owner account. Expected:
/adminopens and shows your email. - Sign out, then load
/admin. Expected: the login page again. - Run
npm run test:signup-closed. Expected: PASS, and the Users list in Supabase still shows one user.
Common problems
- Redirect loop between
/adminand/admin/login→ the login page is itself protected → tell Claude: "/admin/login must be excluded from the redirect; test signed-out and signed-in visits to both addresses". - Sign-in succeeds but
/adminsends you back to login → the session cookie is not being set or refreshed on the server → tell Claude what you see and ask it to compare its setup with Supabase's current Next.js server-side auth guide, step by step. - You are signed out after a short while or at random → the session refresh runs in two places → tell Claude: "the session is refreshed in more than one place; keep the refresh in the proxy only".
- Sign-in is refused because the email is not confirmed → the user was created without confirmation → confirm the user in the Supabase dashboard, or delete and recreate it as confirmed.
test:signup-closedreports FAIL → the switch is still on, or was not saved → turn it off, save, delete the test user, run again.
Homework
About 20 minutes. Keep written answers in your own notes, outside the project folder. Nothing here changes the code.
- See the door open, then close it. In Supabase, turn "Allow new users to sign up" on and run
npm run test:signup-closed: it should report FAIL. Turn the switch off again, save, delete the extra user in the dashboard, and run the script once more. Done when: the script reports PASS and the Users list shows exactly one user, you. Do not stop before this point. - The door from your phone. On your phone, open
/adminon your live domain, sign in with the password from your password manager, then sign out and load/adminagain. Done when: you were sent to the login page before and after, and you did not type the password from memory or from a note. - A "locked out" note. Write three lines: where the owner password is stored, which email address the owner account uses, and which Supabase account owns the project. Do not write the password itself. Done when: you could find all three in a year without guessing.
Save your work
git add -A
git commit -m "Lesson 4.6: admin login and protected admin area"
git tag lesson-4.6
git push
git push --tags