create-authenik8-app 2.4.12 → 2.4.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. package/README.md +160 -57
  2. package/assets/logo.svg +53 -0
  3. package/dist/src/bin/index.js +199 -125
  4. package/dist/src/bin/index.js.map +1 -1
  5. package/dist/src/lib/args.d.ts +18 -0
  6. package/dist/src/lib/args.d.ts.map +1 -0
  7. package/dist/src/lib/args.js +82 -0
  8. package/dist/src/lib/args.js.map +1 -0
  9. package/dist/src/lib/constants.d.ts +1 -0
  10. package/dist/src/lib/constants.d.ts.map +1 -1
  11. package/dist/src/lib/constants.js +10 -11
  12. package/dist/src/lib/constants.js.map +1 -1
  13. package/dist/src/lib/preflight.d.ts +3 -0
  14. package/dist/src/lib/preflight.d.ts.map +1 -0
  15. package/dist/src/lib/preflight.js +21 -0
  16. package/dist/src/lib/preflight.js.map +1 -0
  17. package/dist/src/lib/process.d.ts +6 -2
  18. package/dist/src/lib/process.d.ts.map +1 -1
  19. package/dist/src/lib/process.js +66 -8
  20. package/dist/src/lib/process.js.map +1 -1
  21. package/dist/src/lib/projectName.d.ts +2 -0
  22. package/dist/src/lib/projectName.d.ts.map +1 -0
  23. package/dist/src/lib/projectName.js +6 -0
  24. package/dist/src/lib/projectName.js.map +1 -0
  25. package/dist/src/lib/schemas.d.ts +192 -0
  26. package/dist/src/lib/schemas.d.ts.map +1 -0
  27. package/dist/src/lib/schemas.js +171 -0
  28. package/dist/src/lib/schemas.js.map +1 -0
  29. package/dist/src/lib/state.d.ts.map +1 -1
  30. package/dist/src/lib/state.js +20 -3
  31. package/dist/src/lib/state.js.map +1 -1
  32. package/dist/src/lib/types.d.ts +6 -13
  33. package/dist/src/lib/types.d.ts.map +1 -1
  34. package/dist/src/lib/ui.d.ts +8 -3
  35. package/dist/src/lib/ui.d.ts.map +1 -1
  36. package/dist/src/lib/ui.js +76 -112
  37. package/dist/src/lib/ui.js.map +1 -1
  38. package/dist/src/steps/configurePrisma.d.ts.map +1 -1
  39. package/dist/src/steps/configurePrisma.js +83 -9
  40. package/dist/src/steps/configurePrisma.js.map +1 -1
  41. package/dist/src/steps/createProject.d.ts +2 -1
  42. package/dist/src/steps/createProject.d.ts.map +1 -1
  43. package/dist/src/steps/createProject.js +148 -27
  44. package/dist/src/steps/createProject.js.map +1 -1
  45. package/dist/src/steps/finalSetup.d.ts +3 -3
  46. package/dist/src/steps/finalSetup.d.ts.map +1 -1
  47. package/dist/src/steps/finalSetup.js +24 -28
  48. package/dist/src/steps/finalSetup.js.map +1 -1
  49. package/dist/src/steps/installAuth.d.ts +1 -1
  50. package/dist/src/steps/installAuth.d.ts.map +1 -1
  51. package/dist/src/steps/installAuth.js +5 -4
  52. package/dist/src/steps/installAuth.js.map +1 -1
  53. package/dist/src/steps/installDeps.d.ts +11 -4
  54. package/dist/src/steps/installDeps.d.ts.map +1 -1
  55. package/dist/src/steps/installDeps.js +45 -77
  56. package/dist/src/steps/installDeps.js.map +1 -1
  57. package/dist/src/steps/prompts.d.ts +1 -1
  58. package/dist/src/steps/prompts.d.ts.map +1 -1
  59. package/dist/src/steps/prompts.js +42 -30
  60. package/dist/src/steps/prompts.js.map +1 -1
  61. package/dist/src/utils/jwk.d.ts +2 -0
  62. package/dist/src/utils/jwk.d.ts.map +1 -0
  63. package/dist/src/utils/jwk.js +44 -0
  64. package/dist/src/utils/jwk.js.map +1 -0
  65. package/dist/src/utils/output.d.ts +1 -1
  66. package/dist/src/utils/output.d.ts.map +1 -1
  67. package/dist/src/utils/output.js +75 -68
  68. package/dist/src/utils/output.js.map +1 -1
  69. package/package.json +17 -9
  70. package/templates/AGENT_IDENTITY.md +112 -0
  71. package/templates/THREAT_MODEL.md +27 -5
  72. package/templates/express-auth/README.md +13 -10
  73. package/templates/express-auth/docker-compose.yml +10 -0
  74. package/templates/express-auth/gitignore.template +6 -0
  75. package/templates/express-auth/package.json +12 -5
  76. package/templates/express-auth/src/app.ts +2 -0
  77. package/templates/express-auth/src/controllers/auth.controller.ts +2 -6
  78. package/templates/express-auth/src/controllers/protected.controller.ts +25 -10
  79. package/templates/express-auth/src/middleware/auth.middleware.ts +1 -0
  80. package/templates/express-auth/src/routes/protected.routes.ts +3 -1
  81. package/templates/express-auth/src/server.ts +6 -12
  82. package/templates/express-auth/src/utils/security.ts +119 -32
  83. package/templates/express-auth+/README.md +13 -10
  84. package/templates/express-auth+/docker-compose.yml +10 -0
  85. package/templates/express-auth+/gitignore.template +6 -0
  86. package/templates/express-auth+/package.json +12 -5
  87. package/templates/express-auth+/src/auth/auth.ts +3 -2
  88. package/templates/express-auth+/src/auth/controllers/password.controller.ts +3 -7
  89. package/templates/express-auth+/src/auth/controllers/protected.controller.ts +26 -11
  90. package/templates/express-auth+/src/auth/identity.adapter.ts +90 -0
  91. package/templates/express-auth+/src/auth/middleware/auth.middleware.ts +3 -4
  92. package/templates/express-auth+/src/auth/routes/protected.routes.ts +2 -2
  93. package/templates/express-auth+/src/oauth-providers/github/src/auth/auth.ts +3 -2
  94. package/templates/express-auth+/src/oauth-providers/google/src/auth/auth.ts +3 -2
  95. package/templates/express-auth+/src/oauth-providers/google-github/src/auth/auth.ts +3 -2
  96. package/templates/express-auth+/src/server.ts +5 -10
  97. package/templates/express-auth+/src/utils/security.ts +109 -28
  98. package/templates/express-base/README.md +13 -6
  99. package/templates/express-base/app.ts +2 -0
  100. package/templates/express-base/controllers/base.controller.ts +33 -19
  101. package/templates/express-base/docker-compose.yml +10 -0
  102. package/templates/express-base/gitignore.template +6 -0
  103. package/templates/express-base/package.json +11 -5
  104. package/templates/express-base/routes/base.routes.ts +1 -1
  105. package/templates/express-base/src/.env.example +7 -2
  106. package/templates/express-base/src/server.ts +6 -12
  107. package/templates/express-base/tsconfig.json +7 -40
  108. package/templates/express-base/utils/security.ts +111 -15
  109. package/templates/fullstack/.env.example +24 -0
  110. package/templates/fullstack/PRESET_CONTRACT.md +46 -0
  111. package/templates/fullstack/README.md +65 -0
  112. package/templates/fullstack/THREAT_MODEL.md +30 -0
  113. package/templates/fullstack/apps/api/package.json +48 -0
  114. package/templates/fullstack/apps/api/prisma/schema.prisma +124 -0
  115. package/templates/fullstack/apps/api/prisma/seed.ts +36 -0
  116. package/templates/fullstack/apps/api/prisma.config.ts +18 -0
  117. package/templates/fullstack/apps/api/src/app.ts +68 -0
  118. package/templates/fullstack/apps/api/src/auth/auth.controller.ts +125 -0
  119. package/templates/fullstack/apps/api/src/auth/auth.routes.ts +50 -0
  120. package/templates/fullstack/apps/api/src/auth/auth.service.ts +257 -0
  121. package/templates/fullstack/apps/api/src/auth/authenik8.ts +67 -0
  122. package/templates/fullstack/apps/api/src/auth/cookies.ts +36 -0
  123. package/templates/fullstack/apps/api/src/config/env.ts +72 -0
  124. package/templates/fullstack/apps/api/src/config/logger.ts +17 -0
  125. package/templates/fullstack/apps/api/src/config/mailer.ts +32 -0
  126. package/templates/fullstack/apps/api/src/config/origins.ts +27 -0
  127. package/templates/fullstack/apps/api/src/config/prisma.ts +9 -0
  128. package/templates/fullstack/apps/api/src/middleware/authenticate.ts +35 -0
  129. package/templates/fullstack/apps/api/src/middleware/csrf.ts +60 -0
  130. package/templates/fullstack/apps/api/src/middleware/origin.ts +12 -0
  131. package/templates/fullstack/apps/api/src/middleware/request-id.ts +9 -0
  132. package/templates/fullstack/apps/api/src/modules/admin/admin.controller.ts +17 -0
  133. package/templates/fullstack/apps/api/src/modules/admin/admin.routes.ts +14 -0
  134. package/templates/fullstack/apps/api/src/modules/admin/admin.service.ts +54 -0
  135. package/templates/fullstack/apps/api/src/modules/projects/project.controller.ts +22 -0
  136. package/templates/fullstack/apps/api/src/modules/projects/project.policy.ts +14 -0
  137. package/templates/fullstack/apps/api/src/modules/projects/project.repository.ts +20 -0
  138. package/templates/fullstack/apps/api/src/modules/projects/project.routes.ts +21 -0
  139. package/templates/fullstack/apps/api/src/modules/projects/project.service.ts +41 -0
  140. package/templates/fullstack/apps/api/src/modules/users/user.controller.ts +26 -0
  141. package/templates/fullstack/apps/api/src/modules/users/user.presenter.ts +13 -0
  142. package/templates/fullstack/apps/api/src/modules/users/user.routes.ts +21 -0
  143. package/templates/fullstack/apps/api/src/modules/users/user.service.ts +71 -0
  144. package/templates/fullstack/apps/api/src/openapi.ts +32 -0
  145. package/templates/fullstack/apps/api/src/server.ts +29 -0
  146. package/templates/fullstack/apps/api/src/types.ts +15 -0
  147. package/templates/fullstack/apps/api/src/utils/crypto.ts +4 -0
  148. package/templates/fullstack/apps/api/src/utils/http.ts +33 -0
  149. package/templates/fullstack/apps/api/src/utils/sealed-value.ts +37 -0
  150. package/templates/fullstack/apps/api/tests/project.policy.test.ts +26 -0
  151. package/templates/fullstack/apps/api/tests/registration.test.ts +69 -0
  152. package/templates/fullstack/apps/api/tests/security.test.ts +98 -0
  153. package/templates/fullstack/apps/api/tests/validation.test.ts +37 -0
  154. package/templates/fullstack/apps/api/tsconfig.json +16 -0
  155. package/templates/fullstack/apps/api/vitest.config.ts +9 -0
  156. package/templates/fullstack/apps/web/index.html +14 -0
  157. package/templates/fullstack/apps/web/package.json +31 -0
  158. package/templates/fullstack/apps/web/public/authenik8-logo.svg +53 -0
  159. package/templates/fullstack/apps/web/src/App.tsx +45 -0
  160. package/templates/fullstack/apps/web/src/auth/AuthProvider.tsx +53 -0
  161. package/templates/fullstack/apps/web/src/auth/RouteGuards.tsx +16 -0
  162. package/templates/fullstack/apps/web/src/auth/providers.ts +3 -0
  163. package/templates/fullstack/apps/web/src/components/AppShell.tsx +80 -0
  164. package/templates/fullstack/apps/web/src/components/AuthShell.tsx +26 -0
  165. package/templates/fullstack/apps/web/src/components/Page.tsx +30 -0
  166. package/templates/fullstack/apps/web/src/features/projects/ProjectPages.tsx +79 -0
  167. package/templates/fullstack/apps/web/src/main.tsx +24 -0
  168. package/templates/fullstack/apps/web/src/pages/DashboardPage.tsx +48 -0
  169. package/templates/fullstack/apps/web/src/pages/StatusPages.tsx +8 -0
  170. package/templates/fullstack/apps/web/src/pages/admin/AuditPage.tsx +19 -0
  171. package/templates/fullstack/apps/web/src/pages/admin/UsersPage.tsx +24 -0
  172. package/templates/fullstack/apps/web/src/pages/auth/LoginPage.tsx +51 -0
  173. package/templates/fullstack/apps/web/src/pages/auth/OAuthCallbackPage.tsx +26 -0
  174. package/templates/fullstack/apps/web/src/pages/auth/RecoveryPages.tsx +53 -0
  175. package/templates/fullstack/apps/web/src/pages/auth/RegisterPage.tsx +86 -0
  176. package/templates/fullstack/apps/web/src/pages/auth/VerifyEmailPage.tsx +24 -0
  177. package/templates/fullstack/apps/web/src/pages/settings/ProfilePage.tsx +34 -0
  178. package/templates/fullstack/apps/web/src/pages/settings/SecurityPage.tsx +57 -0
  179. package/templates/fullstack/apps/web/src/styles.css +289 -0
  180. package/templates/fullstack/apps/web/src/token-storage.test.ts +12 -0
  181. package/templates/fullstack/apps/web/tsconfig.app.json +19 -0
  182. package/templates/fullstack/apps/web/tsconfig.json +7 -0
  183. package/templates/fullstack/apps/web/tsconfig.node.json +10 -0
  184. package/templates/fullstack/apps/web/vite.config.ts +18 -0
  185. package/templates/fullstack/docker-compose.yml +35 -0
  186. package/templates/fullstack/docs/PRODUCTION.md +30 -0
  187. package/templates/fullstack/gitignore.template +8 -0
  188. package/templates/fullstack/package.json +39 -0
  189. package/templates/fullstack/packages/api-client/package.json +20 -0
  190. package/templates/fullstack/packages/api-client/src/index.ts +203 -0
  191. package/templates/fullstack/packages/api-client/tsconfig.json +14 -0
  192. package/templates/fullstack/packages/contracts/package.json +20 -0
  193. package/templates/fullstack/packages/contracts/src/index.ts +146 -0
  194. package/templates/fullstack/packages/contracts/tsconfig.json +13 -0
  195. package/templates/fullstack/packages/ui/package.json +22 -0
  196. package/templates/fullstack/packages/ui/src/index.tsx +33 -0
  197. package/templates/fullstack/packages/ui/tsconfig.json +14 -0
  198. package/templates/prisma/postgresql/.env.example +6 -2
  199. package/templates/prisma/postgresql/client.ts +17 -0
  200. package/templates/prisma/postgresql/prisma.config.ts +12 -0
  201. package/templates/prisma/postgresql/schema.prisma +0 -1
  202. package/templates/prisma/sqlite/.env.example +6 -2
  203. package/templates/prisma/sqlite/client.ts +16 -0
  204. package/templates/prisma/sqlite/prisma.config.ts +12 -0
  205. package/templates/prisma/sqlite/schema.prisma +0 -1
  206. package/dist/src/tests/templated-routes.test.d.ts +0 -2
  207. package/dist/src/tests/templated-routes.test.d.ts.map +0 -1
  208. package/dist/src/tests/templated-routes.test.js +0 -325
  209. package/dist/src/tests/templated-routes.test.js.map +0 -1
  210. package/dist/templates/THREAT_MODEL.md +0 -138
  211. package/dist/templates/express-auth/src/controllers/protected.controller.d.ts +0 -8
  212. package/dist/templates/express-auth/src/controllers/protected.controller.d.ts.map +0 -1
  213. package/dist/templates/express-auth/src/controllers/protected.controller.js +0 -50
  214. package/dist/templates/express-auth/src/controllers/protected.controller.js.map +0 -1
  215. package/dist/templates/express-auth/src/routes/protected.routes.d.ts +0 -2
  216. package/dist/templates/express-auth/src/routes/protected.routes.d.ts.map +0 -1
  217. package/dist/templates/express-auth/src/routes/protected.routes.js +0 -16
  218. package/dist/templates/express-auth/src/routes/protected.routes.js.map +0 -1
  219. package/dist/templates/express-auth/src/utils/security.d.ts +0 -8
  220. package/dist/templates/express-auth/src/utils/security.d.ts.map +0 -1
  221. package/dist/templates/express-auth/src/utils/security.js +0 -55
  222. package/dist/templates/express-auth/src/utils/security.js.map +0 -1
  223. package/dist/templates/express-base/controllers/base.controller.d.ts +0 -12
  224. package/dist/templates/express-base/controllers/base.controller.d.ts.map +0 -1
  225. package/dist/templates/express-base/controllers/base.controller.js +0 -77
  226. package/dist/templates/express-base/controllers/base.controller.js.map +0 -1
  227. package/dist/templates/express-base/routes/base.routes.d.ts +0 -2
  228. package/dist/templates/express-base/routes/base.routes.d.ts.map +0 -1
  229. package/dist/templates/express-base/routes/base.routes.js +0 -20
  230. package/dist/templates/express-base/routes/base.routes.js.map +0 -1
  231. package/dist/templates/express-base/utils/security.d.ts +0 -5
  232. package/dist/templates/express-base/utils/security.d.ts.map +0 -1
  233. package/dist/templates/express-base/utils/security.js +0 -40
  234. package/dist/templates/express-base/utils/security.js.map +0 -1
  235. package/dist/templates/package.json +0 -27
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-authenik8-app",
3
- "version": "2.4.12",
3
+ "version": "2.4.14",
4
4
  "description": " Fast Express + TypeScript auth starter with secure JWT, refresh rotation, Redis, RBAC, OAuth & Prisma.\nPowered by the Authenik8 Identity Engine.",
5
5
  "bin": {
6
6
  "create-authenik8-app": "dist/src/bin/index.js"
@@ -28,27 +28,31 @@
28
28
  "license": "MIT",
29
29
  "author": "Karabo Seeisa",
30
30
  "type": "module",
31
+ "engines": {
32
+ "node": "^20.19 || ^22.12 || >=24"
33
+ },
31
34
  "dependencies": {
32
- "authenik8-core": "^1.0.38",
33
- "bun": "^1.3.12",
35
+ "@inquirer/core": "^10.3.2",
34
36
  "chalk": "^5.6.2",
35
37
  "fs-extra": "^11.3.4",
36
- "inquirer": "^13.3.2",
37
- "ora": "^9.3.0",
38
- "tsx": "^4.22.4",
39
- "typescript": "^6.0.3"
38
+ "inquirer": "^12.11.1",
39
+ "ora": "^8.2.0",
40
+ "zod": "^4.4.3"
40
41
  },
41
42
  "files": [
43
+ "assets/logo.svg",
42
44
  "dist/",
43
45
  "templates/"
44
46
  ],
45
47
  "scripts": {
46
- "build": "tsc && cp -r ./templates/express-auth/package.json ./templates/THREAT_MODEL.md dist/templates",
48
+ "clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
49
+ "build": "npm run clean && tsc",
47
50
  "prepublishOnly": "npm run build",
48
51
  "test": "vitest run",
49
52
  "test:watch": "vitest",
50
53
  "test:coverage": "vitest run --coverage",
51
- "test:templates": "node --import tsx --test tests/template-servers.test.mjs"
54
+ "test:templates": "node --import tsx --test tests/template-servers.test.mjs",
55
+ "test:fresh": "node scripts/verify-fresh-project.mjs"
52
56
  },
53
57
  "devDependencies": {
54
58
  "@types/express": "^5.0.6",
@@ -56,8 +60,12 @@
56
60
  "@types/node": "^25.9.3",
57
61
  "@types/supertest": "^7.2.0",
58
62
  "@vitest/coverage-v8": "^4.1.8",
63
+ "authenik8-core": "2.0.3",
59
64
  "fast-check": "^4.8.0",
65
+ "jose": "6.2.3",
60
66
  "supertest": "^7.2.2",
67
+ "tsx": "^4.23.1",
68
+ "typescript": "^6.0.3",
61
69
  "vitest": "^4.1.8"
62
70
  },
63
71
  "repository": {
@@ -0,0 +1,112 @@
1
+ # Agent and service identity
2
+
3
+ Authenik8 separates human access tokens from AI agents, workers, bots, and
4
+ machine-to-machine callers. Agent tokens use a distinct token type, exact
5
+ `resource:action` scopes, an actor chain, and Redis-backed sessions.
6
+
7
+ ## Enable a development registry
8
+
9
+ Agent identity is disabled when `AUTHENIK8_AGENTS` is empty or `{}`. For local
10
+ development, map each agent ID to its maximum grant:
11
+
12
+ ```dotenv
13
+ AUTHENIK8_AGENTS='{"build-worker":["tasks:read","tasks:write"]}'
14
+ ```
15
+
16
+ The generated configuration validates the IDs and scopes before startup. For
17
+ production, replace this static environment registry with a database-backed
18
+ `resolveAgent` callback so deactivation and scope removal take effect without a
19
+ deployment.
20
+
21
+ ## Issue an M2M token from a trusted exchange
22
+
23
+ `issueToken` is a privileged SDK operation, not an authentication endpoint.
24
+ Call it only after authenticating the workload using a mechanism appropriate to
25
+ your infrastructure, such as mTLS, a cloud workload identity, or a signed client
26
+ assertion. Do not expose it as an unauthenticated HTTP route.
27
+
28
+ ```ts
29
+ const agent = auth.agent;
30
+ if (!agent) throw new Error("Agent identity is not configured");
31
+
32
+ const token = await agent.issueToken({
33
+ agentId: "build-worker",
34
+ scopes: ["tasks:read"],
35
+ label: "production queue worker",
36
+ ip: request.ip,
37
+ });
38
+ ```
39
+
40
+ Requested scopes must be an exact subset of the registry grant. Tokens are
41
+ short-lived and stored under a separate `agent-sessions:<agentId>` namespace.
42
+ Human middleware rejects them.
43
+
44
+ ## Protect an agent route
45
+
46
+ ```ts
47
+ import type { AgentAuthenticatedRequest } from "authenik8-core";
48
+
49
+ router.post(
50
+ "/internal/tasks",
51
+ auth.agent!.requireScopes("tasks:write"),
52
+ (req, res) => {
53
+ const actor = (req as AgentAuthenticatedRequest).agent;
54
+ res.json({ accepted: true, actor: actor.agentId });
55
+ },
56
+ );
57
+ ```
58
+
59
+ Every required scope must be present. Scope matching is exact; wildcard scope
60
+ expansion is intentionally not inferred.
61
+
62
+ ## Delegate a human action to an agent
63
+
64
+ Delegation is disabled unless the application supplies `authorizeDelegation`.
65
+ The callback should check the user, tenant, requested operation, consent, and
66
+ any application permission model.
67
+
68
+ ```ts
69
+ const auth = await createAuthenik8({
70
+ // jwt, Redis, and refresh configuration omitted
71
+ agent: {
72
+ resolveAgent: async (agentId) => agentRepository.findActive(agentId),
73
+ authorizeDelegation: async ({ user, agent, requestedScopes }) =>
74
+ user.role === "admin" &&
75
+ agent.agentId === "build-worker" &&
76
+ requestedScopes.every((scope) => scope === "tasks:read"),
77
+ },
78
+ });
79
+
80
+ const delegated = await auth.agent!.issueDelegatedToken({
81
+ agentId: "build-worker",
82
+ userAccessToken,
83
+ scopes: ["tasks:read"],
84
+ });
85
+ ```
86
+
87
+ The token identifies the user as the subject and the agent as the acting party.
88
+ Its `actorChain` records both actors. Revoking the originating human session
89
+ immediately invalidates the delegated token.
90
+
91
+ ## Revoke agent access
92
+
93
+ ```ts
94
+ await auth.agent!.revokeSession("build-worker", sessionId); // one workload session
95
+ await auth.agent!.revokeAgent("build-worker"); // all sessions + issuance block
96
+ await auth.agent!.activateAgent("build-worker"); // clear the core revocation block
97
+ ```
98
+
99
+ The external registry must still report the agent as active. Removing an agent
100
+ or reducing its scopes in that registry also invalidates existing tokens during
101
+ verification.
102
+
103
+ ## Security boundaries
104
+
105
+ - Agent tokens are bearer credentials; protect them in memory and transport.
106
+ - Never place agent tokens in browser storage, URLs, logs, or source control.
107
+ - Keep token lifetimes short and re-authenticate the workload to obtain another.
108
+ - Public JWKS verification proves the signature but cannot by itself observe
109
+ Redis revocation. Security-sensitive APIs should use the session-aware SDK
110
+ middleware or a trusted introspection boundary.
111
+ - A delegated token never outlives the authority of its originating human
112
+ session.
@@ -13,6 +13,7 @@ A generated app includes:
13
13
  - Optional Google/GitHub OAuth.
14
14
  - Authenik8 security middleware: Helmet, rate limiting, and optional IP whitelist.
15
15
  - Optional PM2 production process config.
16
+ - Optional scoped identities for agents, workers, bots, and M2M callers.
16
17
 
17
18
  External systems are outside the generated app boundary:
18
19
 
@@ -33,26 +34,28 @@ External systems are outside the generated app boundary:
33
34
  - User IDs, emails, roles, and provider links.
34
35
  - Redis-backed session state.
35
36
  - Admin-only routes.
37
+ - Agent registry grants, delegated actor chains, and M2M sessions.
36
38
 
37
39
  ## Trust Assumptions
38
40
 
39
- - `JWT_SECRET` and `REFRESH_SECRET` are long, random, private values.
41
+ - The active ES256 private JWK and `REFRESH_SECRET` are random, private, and stored outside source control.
40
42
  - Redis is private to the app and not exposed to the public internet.
41
43
  - Database credentials are private.
42
44
  - OAuth callback URLs match provider dashboard settings exactly.
43
45
  - Production traffic uses HTTPS.
44
46
  - Reverse proxy headers are trusted only when you control the proxy.
45
47
  - Developers validate and authorize their own business-domain routes.
48
+ - Applications authenticate workloads before calling privileged agent-token issuance and explicitly authorize every user delegation.
46
49
 
47
50
  ## Threats Addressed
48
51
 
49
52
  ### Refresh-token replay
50
53
 
51
- Refresh tokens are stateful. The currently valid refresh token is stored in Redis under `refresh:<userId>`. When a refresh succeeds, Authenik8-core rotates the refresh token and replaces the Redis value. Reusing an old refresh token fails.
54
+ Refresh tokens are stateful. The currently valid token for each session is stored under `refresh:<userId>:<sessionId>`. When a refresh succeeds, Authenik8-core atomically replaces it. Reusing an old refresh token fails and revokes that refresh family.
52
55
 
53
56
  ### Concurrent refresh abuse
54
57
 
55
- Refresh requests acquire a Redis lock under `lock:<userId>`. Two simultaneous refresh attempts for the same user should not both rotate successfully.
58
+ Refresh requests acquire a Redis lock under `lock:<userId>:<sessionId>`. Two simultaneous refresh attempts for the same session cannot both rotate successfully.
56
59
 
57
60
  ### Stateless JWT logout limitations
58
61
 
@@ -76,12 +79,21 @@ OAuth profiles are normalized into provider, provider ID, email, name, and email
76
79
 
77
80
  ### Unverified OAuth email token issuance
78
81
 
79
- `issueTokensFromProfile` rejects OAuth profiles whose email is not verified.
82
+ Provider callbacks verify the provider email before the Identity Engine issues an application session.
80
83
 
81
84
  ### Admin-route access
82
85
 
83
86
  `auth.requireAdmin` checks for a valid JWT with `role: "admin"`.
84
87
 
88
+ ### Agent identity and revocation
89
+
90
+ Human and agent tokens have distinct purposes and middleware. Agent scopes must
91
+ be an exact subset of the live registry grant. M2M sessions are stored under
92
+ `agent-sessions:<agentId>`, and removing a grant, revoking one session, or
93
+ revoking the whole agent invalidates its tokens. Delegated tokens record the
94
+ human and agent actor chain and become invalid when the originating human
95
+ session is revoked.
96
+
85
97
  ## Threats Not Fully Addressed
86
98
 
87
99
  ### XSS in your frontend
@@ -94,7 +106,7 @@ Generated examples use bearer tokens. If you move tokens into cookies, add CSRF
94
106
 
95
107
  ### Weak or leaked secrets
96
108
 
97
- Authenik8 cannot protect tokens if `JWT_SECRET` or `REFRESH_SECRET` is short, reused, committed, logged, or leaked.
109
+ Authenik8 cannot protect tokens if the private signing JWK or `REFRESH_SECRET` is committed, logged, reused, or leaked. Public keys from `/.well-known/jwks.json` are safe to distribute.
98
110
 
99
111
  ### Public Redis exposure
100
112
 
@@ -120,6 +132,15 @@ Global rate limiting is included. Add stricter per-email or per-account login th
120
132
 
121
133
  Short-lived access tokens reduce exposure, but a stolen access token can be used until it expires or is rejected by your session policy.
122
134
 
135
+ ### Workload authentication and offline agent verification
136
+
137
+ The SDK issues agent tokens only after trusted application code calls it; your
138
+ application must authenticate the workload using mTLS, cloud workload identity,
139
+ a signed assertion, or another suitable credential. Never expose issuance as an
140
+ unauthenticated route. Agent tokens remain bearer credentials. Public JWKS
141
+ verification cannot observe Redis revocation without a trusted introspection or
142
+ session-aware middleware boundary.
143
+
123
144
  ## Production Checklist
124
145
 
125
146
  - Replace generated development secrets with long random values.
@@ -131,6 +152,7 @@ Short-lived access tokens reduce exposure, but a stolen access token can be used
131
152
  - Review CORS policy before connecting a frontend.
132
153
  - Add business-level authorization checks to every protected resource route.
133
154
  - Add logging and alerting for refresh failures, OAuth failures, and admin actions.
155
+ - Keep `AUTHENIK8_AGENTS={}` unless agent identity is intentionally configured; review every agent grant and delegation policy.
134
156
  - Keep `authenik8-core` and generated dependencies updated.
135
157
 
136
158
  ## Security Reporting
@@ -15,14 +15,18 @@ For SQLite, Postgres in `docker-compose.yml` is optional. Redis is required for
15
15
 
16
16
  ## Environment
17
17
 
18
- Review `.env` before running. The generated secrets are development placeholders and must be replaced before deployment.
18
+ The generator writes a unique signing key and refresh secret to git-ignored `.env`. Move them into your deployment secret manager and never commit the private JWK.
19
19
 
20
20
  Required:
21
21
 
22
22
  ```bash
23
23
  DATABASE_URL=file:./dev.db
24
- JWT_SECRET=dev-jwt-secret-change-before-production-123456
25
- REFRESH_SECRET=dev-refresh-secret-change-before-production-123456
24
+ AUTHENIK8_SIGNING_JWKS='[{"kty":"EC","crv":"P-256","kid":"<generated>","x":"...","y":"...","d":"...","alg":"ES256"}]'
25
+ AUTHENIK8_ACTIVE_KID=<generated>
26
+ AUTHENIK8_ISSUER=http://localhost:3000
27
+ AUTHENIK8_AUDIENCE=your-app-api
28
+ AUTHENIK8_AGENTS={}
29
+ REFRESH_SECRET=<generated-random-secret>
26
30
  REDIS_HOST=127.0.0.1
27
31
  REDIS_PORT=6379
28
32
  ```
@@ -101,10 +105,6 @@ Expected shape:
101
105
 
102
106
  ```json
103
107
  {
104
- "user": {
105
- "id": "user-id",
106
- "email": "dev@example.com"
107
- },
108
108
  "accessToken": "access-token",
109
109
  "refreshToken": "refresh-token"
110
110
  }
@@ -128,7 +128,10 @@ curl -s -X POST http://localhost:3000/auth/refresh \
128
128
  ## Environment Variables
129
129
 
130
130
  - `DATABASE_URL`: Prisma database connection. SQLite uses `file:./dev.db`; Postgres uses a `postgresql://...` URL.
131
- - `JWT_SECRET`: signs short-lived access tokens. Use a long random value in production.
131
+ - `AUTHENIK8_SIGNING_JWKS`: JSON array containing the active private ES256 key and any previous public verification keys.
132
+ - `AUTHENIK8_ACTIVE_KID`: selects the private key used to sign new access tokens.
133
+ - `AUTHENIK8_ISSUER` / `AUTHENIK8_AUDIENCE`: required claims checked during verification.
134
+ - `AUTHENIK8_AGENTS`: optional JSON map of agent IDs to exact `resource:action` scopes; keep `{}` to disable agent identity. See `AGENT_IDENTITY.md`.
132
135
  - `REFRESH_SECRET`: signs refresh tokens. Use a different long random value in production.
133
136
  - `REDIS_HOST`: Redis host for refresh-token/session security.
134
137
  - `REDIS_PORT`: Redis port, usually `6379` locally.
@@ -177,13 +180,13 @@ export async function getProtected() {
177
180
 
178
181
  `Prisma Client did not initialize`: run `npm run prisma:migrate`, then restart `npm run dev`.
179
182
 
180
- `JWT_SECRET must be set to at least 32 characters`: check `.env`; both token secrets must be long strings.
183
+ `AUTHENIK8_SIGNING_JWKS must be a valid JSON array`: restore the generated `.env` value or provide an ES256 P-256 key ring and matching `AUTHENIK8_ACTIVE_KID`.
181
184
 
182
185
  `Cannot POST /auth/login`: confirm the server is running and you generated the password auth template, not the JWT-only base template.
183
186
 
184
187
  `Invalid email or password`: register the user first, then login with the same email/password.
185
188
 
186
- `Port 3000 already in use`: stop the other process or change the `app.listen(3000)` port in `src/server.ts`.
189
+ `Port 3000 already in use`: stop the other process or set `PORT` in `.env`.
187
190
 
188
191
  `DATABASE_URL is wrong`: for SQLite use `file:./dev.db`; for local Docker Postgres use `postgresql://postgres:postgres@localhost:5432/authenik8?schema=public`.
189
192
 
@@ -6,6 +6,11 @@ services:
6
6
  command: ["redis-server", "--appendonly", "yes"]
7
7
  volumes:
8
8
  - redis-data:/data
9
+ healthcheck:
10
+ test: ["CMD", "redis-cli", "ping"]
11
+ interval: 5s
12
+ timeout: 3s
13
+ retries: 10
9
14
 
10
15
  postgres:
11
16
  image: postgres:16-alpine
@@ -17,6 +22,11 @@ services:
17
22
  POSTGRES_DB: authenik8
18
23
  volumes:
19
24
  - postgres-data:/var/lib/postgresql/data
25
+ healthcheck:
26
+ test: ["CMD-SHELL", "pg_isready -U postgres -d authenik8"]
27
+ interval: 5s
28
+ timeout: 3s
29
+ retries: 10
20
30
 
21
31
  volumes:
22
32
  redis-data:
@@ -0,0 +1,6 @@
1
+ node_modules/
2
+ dist/
3
+ .env
4
+ *.log
5
+ coverage/
6
+ .DS_Store
@@ -3,25 +3,32 @@
3
3
  "description": "Authenik8 generated Express auth app",
4
4
  "main": "dist/server.js",
5
5
  "type": "commonjs",
6
+ "engines": {
7
+ "node": "^20.19 || ^22.12 || >=24"
8
+ },
6
9
  "scripts": {
7
10
  "dev": "ts-node-dev --respawn --transpile-only src/server.ts",
8
11
  "build": "tsc",
9
12
  "start": "node dist/server.js",
10
- "prisma:migrate": "prisma migrate dev",
11
- "docker:up": "docker compose up -d",
13
+ "prisma:migrate": "prisma migrate dev && prisma generate",
14
+ "docker:up": "docker compose up -d --wait",
12
15
  "docker:down": "docker compose down"
13
16
  },
14
17
  "dependencies": {
15
- "authenik8-core": "^1.0.38",
18
+ "authenik8-core": "2.0.3",
16
19
  "dotenv": "^16.0.0",
17
20
  "express": "^4.19.2",
18
- "@prisma/client": "5.22.0"
21
+ "@prisma/client": "7.8.0",
22
+ "zod": "^4.4.3"
19
23
  },
20
24
  "devDependencies": {
21
25
  "@types/express": "^4.17.21",
22
26
  "@types/node": "^26.0.1",
23
27
  "ts-node-dev": "^2.0.0",
24
28
  "typescript": "^5.0.0",
25
- "prisma": "5.22.0"
29
+ "prisma": "7.8.0"
30
+ },
31
+ "overrides": {
32
+ "@hono/node-server": "1.19.13"
26
33
  }
27
34
  }
@@ -10,6 +10,8 @@ export const createApp = (auth: any) => {
10
10
  app.use(auth.helmet);
11
11
  app.use(auth.rateLimit);
12
12
 
13
+ app.get("/.well-known/jwks.json", (_req, res) => res.json(auth.getJwks()));
14
+
13
15
  app.use("/auth", createAuthRoutes(auth));
14
16
  app.use("/", createProtectedRoutes(auth));
15
17
 
@@ -21,14 +21,10 @@ export const createAuthController = (auth: any) => ({
21
21
 
22
22
  const user = await AuthService.login(email, password);
23
23
 
24
- const accessToken = auth.signToken({
25
- userId: user.id,
26
- email: user.email,
27
- });
28
-
29
- const refreshToken = await auth.generateRefreshToken({
24
+ const { accessToken, refreshToken } = await auth.issueTokens({
30
25
  userId: user.id,
31
26
  email: user.email,
27
+ role: String(user.role).toLowerCase(),
32
28
  });
33
29
 
34
30
  res.json({ accessToken, refreshToken });
@@ -1,5 +1,16 @@
1
1
  import { Request, Response } from "express";
2
- import { sanitizeSessionResponse } from "../utils/security";
2
+ import {
3
+ InputValidationError,
4
+ parseIdentifier,
5
+ sanitizeSessionResponse,
6
+ } from "../utils/security";
7
+
8
+ function sessionError(res: Response, error: unknown, fallback: string) {
9
+ if (error instanceof InputValidationError) {
10
+ return res.status(400).json({ success: false, message: error.message });
11
+ }
12
+ return res.status(500).json({ success: false, message: fallback });
13
+ }
3
14
 
4
15
  export const createProtectedController = () => ({
5
16
  protected(req: Request, res: Response) {
@@ -13,10 +24,11 @@ export const createProtectedController = () => ({
13
24
  return res.status(503).json({ success: false, message: "Session management unavailable" });
14
25
  }
15
26
 
16
- const sessions = await actions.listSessions(req.params.userId);
27
+ const userId = parseIdentifier(req.params.userId, "User ID");
28
+ const sessions = await actions.listSessions(userId);
17
29
  res.json({ sessions: sanitizeSessionResponse(sessions) });
18
- } catch {
19
- res.status(500).json({ success: false, message: "Failed to retrieve sessions" });
30
+ } catch (error) {
31
+ sessionError(res, error, "Failed to retrieve sessions");
20
32
  }
21
33
  },
22
34
 
@@ -27,10 +39,12 @@ export const createProtectedController = () => ({
27
39
  return res.status(503).json({ success: false, message: "Session management unavailable" });
28
40
  }
29
41
 
30
- await actions.revokeSession(req.params.userId, req.params.sessionId);
42
+ const userId = parseIdentifier(req.params.userId, "User ID");
43
+ const sessionId = parseIdentifier(req.params.sessionId, "Session ID");
44
+ await actions.revokeSession(userId, sessionId);
31
45
  res.json({ success: true, message: "Session revoked" });
32
- } catch {
33
- res.status(500).json({ success: false, message: "Failed to revoke session" });
46
+ } catch (error) {
47
+ sessionError(res, error, "Failed to revoke session");
34
48
  }
35
49
  },
36
50
 
@@ -41,10 +55,11 @@ export const createProtectedController = () => ({
41
55
  return res.status(503).json({ success: false, message: "Session management unavailable" });
42
56
  }
43
57
 
44
- await actions.revokeAllSessions(req.params.userId);
58
+ const userId = parseIdentifier(req.params.userId, "User ID");
59
+ await actions.revokeAllSessions(userId);
45
60
  res.json({ success: true, message: "All sessions revoked" });
46
- } catch {
47
- res.status(500).json({ success: false, message: "Failed to revoke sessions" });
61
+ } catch (error) {
62
+ sessionError(res, error, "Failed to revoke sessions");
48
63
  }
49
64
  },
50
65
  });
@@ -0,0 +1 @@
1
+ export const createAuthMiddleware = (auth: any) => auth.requireAuth;
@@ -1,11 +1,13 @@
1
1
  import { Router } from "express";
2
2
  import { createProtectedController } from "../controllers/protected.controller";
3
+ import { createAuthMiddleware } from "../middleware/auth.middleware";
3
4
 
4
5
  export const createProtectedRoutes = (auth: any) => {
5
6
  const router = Router();
6
7
  const controller = createProtectedController();
8
+ const requireAuth = createAuthMiddleware(auth);
7
9
 
8
- router.get("/protected", auth.requireAdmin, controller.protected);
10
+ router.get("/protected", requireAuth, controller.protected);
9
11
  router.get("/admin/sessions/:userId", auth.requireAdmin, controller.listSessions);
10
12
  router.delete("/admin/sessions/:userId/:sessionId", auth.requireAdmin, controller.revokeSession);
11
13
  router.delete("/admin/sessions/:userId", auth.requireAdmin, controller.revokeAllSessions);
@@ -1,20 +1,22 @@
1
1
  import dotenv from "dotenv";
2
2
  import { createAuthenik8 } from "authenik8-core";
3
3
  import { createApp } from "./app";
4
- import { requiredSecret } from "./utils/security";
4
+ import { agentIdentityConfig, authJwkConfig, requiredPort, requiredSecret } from "./utils/security";
5
5
 
6
6
  dotenv.config();
7
7
 
8
8
  async function start() {
9
9
  const auth = await createAuthenik8({
10
- jwtSecret: requiredSecret("JWT_SECRET"),
10
+ jwt: authJwkConfig(),
11
11
  refreshSecret: requiredSecret("REFRESH_SECRET"),
12
+ agent: agentIdentityConfig(),
12
13
  });
13
14
 
14
15
  const app = createApp(auth);
15
16
 
16
- app.listen(3000, () => {
17
- console.log(" Server running on http://localhost:3000");
17
+ const port = requiredPort();
18
+ app.listen(port, () => {
19
+ console.log(` Server running on http://localhost:${port}`);
18
20
  });
19
21
  }
20
22
  process.on("uncaughtException", (err) => {
@@ -27,12 +29,4 @@ process.on("unhandledRejection", (err) => {
27
29
  process.exit(1);
28
30
 
29
31
  });
30
- setInterval(() => {
31
- const used = process.memoryUsage().heapUsed / 1024 / 1024;
32
-
33
- if (used > 300) {
34
- console.error(`Memory exceeded: ${used.toFixed(2)} MB`);
35
- process.exit(1);
36
- }
37
- }, 10000);
38
32
  start();