secure-a-frontend-app
Skill1 starsChanged 5 months agoMITView on GitHub
Use when wiring auth/session, protecting routes, or handling secrets in a frontend app — OIDC login via NextAuth + server session store + access-token refresh & injection, route guards + permission-matrix RBAC, SSR cookie propagation + hydration, and vault secrets + public/private config split. React/Next.js reference, framework-flexible.
- Reads credentials
In this repository40 other files it gives its agents.
- authoring-project-skillsSkill
- background-jobs-and-cachingSkill
- code-conventionsSkill
- containerize-and-ship-a-serviceSkill
- database-migrationsSkill
- design-an-error-modelSkill
- git-flowSkill
- handle-files-frontendSkill
- import-data-from-csvSkill
- integrate-external-servicesSkill
- integrate-identity-providersSkill
- integrate-internal-servicesSkill
- keep-service-memory-leanSkill
- operate-ecs-services-safelySkill
- prevent-secret-and-pii-leaksSkill
- release-safetySkill
- render-transactional-emailsSkill
- rules-backendSkill
- rules-code-styleSkill
- rules-debuggingSkill
- rules-gitSkill
- rules-infraSkill
- rules-pr-reviewSkill
- rules-shippingSkill
- rules-workflowSkill
- schedule-effective-dated-changesSkill
- security-auditSkill
- send-slackSkill
- serve-realtime-with-websocketsSkill
- structure-a-backend-serviceSkill
- structure-a-frontend-appSkill
- structure-a-shared-backend-libSkill
- structure-a-shared-ui-libSkill
- tdd-http-firstSkill
- use-feature-flagsSkill
- write-cross-cutting-codeSkill
- write-frontend-codeSkill
- write-frontend-testsSkill
- write-service-codeSkill
- write-unit-testsSkill
The file
Files
Secure a frontend app
Auth, session, access control, and secrets for a web app. App structure: structure-a-frontend-app;
feature code: write-frontend-code.
1. Auth & session
- OIDC via NextAuth. Configure the provider (with PKCE/nonce checks) in the auth route; store the session server-side (a session-store adapter, e.g. Redis) rather than a fat JWT cookie.
- Refresh in the session callback. On each
sessionresolve, check expiry and rotate the access token before handing it to the client:async session({ session, user }) { const acct = await store.getAccount(user.id); if (Date.parse(acct.expires_at) < Date.now()) { const next = await refreshAccessToken(acct); // rotate session.accessToken = next.access_token; } return session; } - One client-side entry injects the token into the HTTP client: a
useAuthhook reads the session and callsBaseHttp.saveToken(session.accessToken)(the HTTP client is inwrite-frontend-code§2). - 401 → one queued refresh + replay at the HTTP layer (the refresh QUEUE in
write-frontend-code§2): concurrent 401s wait on a single refresh, then retry. ▸ Other stacks: any OIDC lib (Auth.js / oidc-client) + a server session store; rotate in the session hook, inject once into the HTTP client, queue-refresh on 401.
2. SSR cookie propagation + hydration
- Pass auth cookies from SSR into the app via the app-init hook, then read them in providers:
MyApp.getInitialProps = async (ctx) => ({ cookies: parseCookies(ctx.req), origin: ctx.req.headers.host }); // _app: <SessionProvider session={pageProps.session}><LocalizationProvider cookies={cookies}>… - Guard token init behind hydration — don't read the session until the router/session is ready:
useEffect(() => { if (!router.isReady || status === 'loading') return; if (session) BaseHttp.saveToken(session.accessToken); }, [router.isReady, status]);
▸ Other stacks: read auth cookies server-side, hand to a provider; gate client token use on "ready/mounted".
3. Route protection & RBAC
- Roles + permissions come from one profile hook (
useMyProfile→ memoized role booleans + apermissions: string[]). Don't re-derive roles ad hoc per component. - Permission matrix as
feature.actionstrings. Gate UI with a checker, routes with a guard:// component-level visibility can({ feature: 'listing', action: 'create' }); // → permissions.includes('listing.create') // route-level (a GuardContainer wrapping the app) const allowed = routesForPermissions(permissions).includes(currentRoute); if (!allowed && role) { confirm('Access denied'); router.replace(Routes.Home); } middleware.tshandles edge redirects/rewrites (e.g. a cookie-driven path → canonical route); keep it thin — real auth/role logic lives in the guard + matrix, not middleware. ▸ Other stacks: a route-guard wrapper + a permission map; render-gate byfeature.action, redirect on deny.
4. Secrets & config safety
- Server secrets from a vault, never the bundle. Load server-only env from a mounted secrets path
at boot (fall back to
.envlocally):// next.config.js const paths = ['/vault/secrets/global', '/vault/secrets/app']; paths.every(fs.existsSync) ? paths.forEach((p) => dotenv.config({ path: p })) : dotenv.config(); - Public vs private split in one Config module — only
NEXT_PUBLIC_*(client-exposed) values go in the public group; secrets (client secret, store password, service keys) stay server-only. Mark server-only vars in.env.example("do not useNEXT_PUBLIC_— never expose to the browser"). - A value reaches the browser bundle only if it is
NEXT_PUBLIC_-prefixed AND in the public Config group — audit both sides. ▸ Other stacks: the framework's public-env prefix + a settings module separating server secrets; pull secrets from a vault/secret-manager, not committed env.
Verification
- Login goes through the OIDC provider; tokens are stored server-side; the session callback refreshes expiring tokens; the client injects the token once; a 401 triggers one queued refresh + replay.
- Roles/permissions come from the profile hook; UI gates on
feature.action; a route guard redirects on deny;middleware.tsstays thin. grep -rn "NEXT_PUBLIC_" srcshows only client-safe values; secrets load from the vault path; no secret sits in the public Config group or the client bundle.
Related
write-frontend-code— theBaseHttp401-refresh queue, SSR guards, config module.structure-a-frontend-app·code-conventions·release-safety(backend release/secrets).
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

