{"_id":"@agenciapine/auth-next","name":"@agenciapine/auth-next","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@agenciapine/auth-next","version":"0.1.0","description":"Cliente de SSO da Agência Pine para apps Next.js (OIDC Authorization Code + PKCE)","license":"MIT","author":{"name":"Agência Pine","email":"conteudo@agenciapine.com.br"},"homepage":"https://github.com/kelwinpinheiro/pine-autenticacao#readme","repository":{"type":"git","url":"git+https://github.com/kelwinpinheiro/pine-autenticacao.git","directory":"packages/auth-next"},"bugs":{"url":"https://github.com/kelwinpinheiro/pine-autenticacao/issues"},"engines":{"node":">=20"},"type":"module","sideEffects":false,"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./proxy":{"types":"./dist/proxy.d.ts","import":"./dist/proxy.js","require":"./dist/proxy.cjs"},"./react":{"types":"./dist/react.d.ts","import":"./dist/react.js","require":"./dist/react.cjs"}},"scripts":{"build":"rm -rf dist && tsup","dev":"tsup --watch","typecheck":"tsc --noEmit","test":"vitest run","prepublishOnly":"pnpm build"},"dependencies":{"jose":"^6.2.3"},"peerDependencies":{"next":">=15 <17","react":">=18"},"peerDependenciesMeta":{"react":{"optional":true}},"devDependencies":{"@types/node":"^20","@types/react":"^19","next":"16.2.10","react":"19.2.4","tsup":"^8.5.0","typescript":"^5","vitest":"^4.1.10"},"publishConfig":{"access":"public"},"keywords":["oidc","sso","next.js","auth"],"gitHead":"a387445e55eaabb08ec128cf8fca45c6358aa077","_id":"@agenciapine/auth-next@0.1.0","_nodeVersion":"24.10.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-ASCg85MHa8tC/jHbVPwqJK6/5+6fNh4Kr/fG1hoge4XMQIC+/xNSe/tQbKeztVpxLVX1JHYMLgUoPf798kF/gQ==","shasum":"246f11793a9f21b018ce2c00789260e091176ffa","tarball":"https://registry.npmjs.org/@agenciapine/auth-next/-/auth-next-0.1.0.tgz","fileCount":29,"unpackedSize":307610,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIBIyrvagU8nn8Mdi7DHsG/lLitn9JIrkMyqsyliVXctIAiEA0QdRsFKc7HMSjQHUiBNrd0nHmii3OlfgNQauHtt0Ghs="}]},"_npmUser":{"name":"agenciapine","email":"contato@agenciapine.com.br"},"directories":{},"maintainers":[{"name":"agenciapine","email":"contato@agenciapine.com.br"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/auth-next_0.1.0_1786808686098_0.46738319898547"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-15T15:44:45.999Z","0.1.0":"2026-08-15T15:44:46.262Z","modified":"2026-08-15T15:44:46.431Z"},"maintainers":[{"name":"agenciapine","email":"contato@agenciapine.com.br"}],"description":"Cliente de SSO da Agência Pine para apps Next.js (OIDC Authorization Code + PKCE)","homepage":"https://github.com/kelwinpinheiro/pine-autenticacao#readme","keywords":["oidc","sso","next.js","auth"],"repository":{"type":"git","url":"git+https://github.com/kelwinpinheiro/pine-autenticacao.git","directory":"packages/auth-next"},"author":{"name":"Agência Pine","email":"conteudo@agenciapine.com.br"},"bugs":{"url":"https://github.com/kelwinpinheiro/pine-autenticacao/issues"},"license":"MIT","readme":"# @agenciapine/auth-next\n\nCliente de SSO da Agência Pine para apps Next.js. Esconde OIDC\n(Authorization Code + PKCE) atrás de três funções.\n\nFunciona no **Next 15** (`src/middleware.ts`) e no **Next 16**\n(`src/proxy.ts`) — é a mesma função, só o nome do arquivo mudou entre as\nversões.\n\n## Instalação\n\n```bash\nnpm install @agenciapine/auth-next\n```\n\n```bash\n# .env\nPINE_AUTH_ISSUER=https://auth.agenciapine.com.br\nPINE_AUTH_CLIENT_ID=seu-app\nPINE_AUTH_CLIENT_SECRET=\nPINE_SESSION_SECRET=            # openssl rand -base64 32\nNEXT_PUBLIC_APP_URL=https://seu-app.agenciapine.com.br\n```\n\n## Os três arquivos\n\n```ts\n// src/lib/pine-auth.ts\nimport { createPineAuth } from \"@agenciapine/auth-next\";\n\nexport const pineAuth = createPineAuth({\n  issuer: process.env.PINE_AUTH_ISSUER!,\n  clientId: process.env.PINE_AUTH_CLIENT_ID!,\n  clientSecret: process.env.PINE_AUTH_CLIENT_SECRET!,\n  appUrl: process.env.NEXT_PUBLIC_APP_URL!,\n  sessionSecret: process.env.PINE_SESSION_SECRET!,\n});\n\nexport const { getSession, requireAuth, requireRole, signOut } = pineAuth;\n```\n\n```ts\n// src/app/api/auth/[...pine]/route.ts\nimport { pineAuth } from \"@/lib/pine-auth\";\n\nexport const { GET, POST } = pineAuth.handlers;\nexport const runtime = \"nodejs\";\nexport const dynamic = \"force-dynamic\";\n```\n\n```ts\n// src/proxy.ts (Next 16)  |  src/middleware.ts (Next 15)\nimport { pineProxy } from \"@agenciapine/auth-next/proxy\";\n\nexport default pineProxy({\n  issuer: process.env.PINE_AUTH_ISSUER!,\n  clientId: process.env.PINE_AUTH_CLIENT_ID!,\n  clientSecret: process.env.PINE_AUTH_CLIENT_SECRET!,\n  appUrl: process.env.NEXT_PUBLIC_APP_URL!,\n  sessionSecret: process.env.PINE_SESSION_SECRET!,\n  protect: [\"/painel/:path*\"],\n  roles: { \"/admin/:path*\": [\"admin\"] },\n});\n\nexport const config = {\n  matcher: [\"/((?!_next/static|_next/image|favicon.ico).*)\"],\n};\n```\n\n> **Não importe `@/lib/pine-auth` no proxy.** Isso arrastaria o hook de\n> provisioning — e com ele o ORM — para dentro do middleware, que em Next 15\n> roda no Edge.\n\n## Uso\n\n```tsx\n// Server Component\nconst { user } = await requireAuth();\n<p>{user.name} — {user.role}</p>\n\n// Rota que exige papel\nawait requireRole([\"admin\", \"gestor\"]);\n\n// Antes de uma ação de alto impacto: valida contra o servidor agora\nawait requireFresh();\n\n// Chamar uma API com o token\nconst res = await pineAuth.fetchWithAuth(\"https://api.exemplo.com/dados\");\n```\n\nClient Components (opcional):\n\n```tsx\n// layout.tsx\n<PineAuthProvider session={await getSession()}>{children}</PineAuthProvider>\n\n// componente\nconst { user, hasRole, signOut } = usePineSession();\n```\n\n`hasRole` no cliente serve para **UI** (esconder um botão). Toda decisão de\nsegurança fica no servidor: um usuário pode editar o estado do React, não pode\neditar o que `requireRole()` faz num Server Component.\n\n## Ligando ao usuário local do app\n\nApps com tabela `User` própria usam o hook `afterLogin` para vincular a\nidentidade global ao registro local — uma vez por login, não a cada request:\n\n```ts\ncreatePineAuth({\n  // ...\n  afterLogin: async ({ claims }) => ({\n    localUserId: await linkUser(claims),\n  }),\n});\n```\n\nDepois disso, `session.user.localUserId` é o valor a usar como foreign key.\n`session.user.id` é o `sub` do SSO.\n\n## Comportamentos que costumam surpreender\n\n**Revogar um acesso leva até 15 minutos para propagar.** O `role` viaja dentro\ndo access token. Para efeito imediato use `requireFresh()` (consulta o\n`/introspect`) ou ligue um `sessionStore`.\n\n**O refresh acontece no proxy, não no Server Component.** RSC não pode gravar\ncookie. O proxy renova e repassa a sessão nova ao render do mesmo request por\nheader — por isso vale a pena configurar o proxy mesmo em apps simples.\n\n**Logout global sem `sessionStore` leva até 15 min no pior caso.** O refresh\ntoken já foi revogado, então a sessão morre no máximo em um TTL. Com\n`backchannelLogoutUri` registrado no painel, o app é avisado na hora.\n\n**A config é validada no primeiro uso, não na importação.** É o que permite\n`next build` rodar em CI e no `docker build` sem as variáveis de runtime.\n\n## Segurança implementada\n\nPKCE S256 obrigatório · `state` comparado em tempo constante · `nonce`\nverificado contra o ID token · `aud` validado contra o próprio `clientId`\n(impede token de um app valer em outro) · algoritmo restrito a RS256 ·\n`?redirect=` só aceita caminho relativo · cookie de sessão cifrado (JWE\nA256GCM, chave derivada por HKDF) · `Cache-Control: no-store` nas rotas de auth\n· rotação de `sessionSecret` sem deslogar ninguém (passe um array) · dedupe de\nrefresh concorrente.\n","readmeFilename":"README.md","_rev":"1-c0416efca2b5b6ba5390a1c27b694e32"}