agentsclimarketplace

Secure a frontend app

Skill kennguyen887/agent-foundation/skills/secure-a-frontend-app

Claude Code skills marketplace — backend & frontend engineering conventions + step-by-step third-party integration recipes: Stripe, Rapyd, CyberSource, UOB & wallet payments, Singpass/Keycloak OIDC & 3-D Secure, Twilio SMS, Docker & CI/CD. NestJS/TypeScript + React, language-flexible.

Install
npx -y skills add kennguyen887/agent-foundation --skill secure-a-frontend-app

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

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.

SKILL.md

5.1 KB, as published. Nobody here has run it

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 session resolve, 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 useAuth hook reads the session and calls BaseHttp.saveToken(session.accessToken) (the HTTP client is in write-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 + a permissions: string[]). Don't re-derive roles ad hoc per component.
  • Permission matrix as feature.action strings. 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.ts handles 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 by feature.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 .env locally):
    // 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 use NEXT_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.ts stays thin.
  • grep -rn "NEXT_PUBLIC_" src shows 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 — the BaseHttp 401-refresh queue, SSR guards, config module.
  • structure-a-frontend-app · code-conventions · release-safety (backend release/secrets).

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.