legate 0.1.0

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 (317) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +345 -0
  4. data/bin/legate +13 -0
  5. data/examples/00_quickstart.rb +51 -0
  6. data/examples/01_simple_agent.rb +105 -0
  7. data/examples/02_multi_tool_agent.rb +140 -0
  8. data/examples/03_custom_tool.rb +93 -0
  9. data/examples/04_agent_instructions.rb +84 -0
  10. data/examples/05_state_and_sessions.rb +91 -0
  11. data/examples/06_callbacks.rb +186 -0
  12. data/examples/07_async_jobs.rb +112 -0
  13. data/examples/08_loop_agent.rb +197 -0
  14. data/examples/09_sequential_workflow.rb +40 -0
  15. data/examples/10_parallel_workflow.rb +34 -0
  16. data/examples/11_agent_delegation.rb +24 -0
  17. data/examples/12_http_client_tool.rb +156 -0
  18. data/examples/13_authentication.rb +220 -0
  19. data/examples/14_mcp_client.rb +154 -0
  20. data/examples/15_mcp_server.rb +79 -0
  21. data/examples/16_webhooks.rb +91 -0
  22. data/examples/README_sequential_agents.md +164 -0
  23. data/examples/advanced/auth/cookie_auth_tool.rb +146 -0
  24. data/examples/advanced/auth/custom_auth_flows_example.rb +626 -0
  25. data/examples/advanced/auth/excon_middleware.rb +317 -0
  26. data/examples/advanced/auth/excon_middleware_auth.rb +399 -0
  27. data/examples/advanced/auth/fiber_auth_example.rb +281 -0
  28. data/examples/advanced/auth/fiber_oidc_example.rb +403 -0
  29. data/examples/advanced/auth/httpbin_bearer_tool.rb +159 -0
  30. data/examples/advanced/auth/oauth2_auth.rb +419 -0
  31. data/examples/advanced/auth/oidc_auth.rb +514 -0
  32. data/examples/advanced/auth/openweather_api.rb +251 -0
  33. data/examples/advanced/auth/openweather_tool.rb +153 -0
  34. data/examples/advanced/auth/query_param_middleware_test.rb +138 -0
  35. data/examples/advanced/auth/service_account.rb +135 -0
  36. data/examples/advanced/auth/test_with_httpbin.rb +202 -0
  37. data/examples/advanced/auth/token_lifecycle_example.rb +428 -0
  38. data/examples/advanced/callback_monitoring.rb +679 -0
  39. data/examples/advanced/mas/fixed_delegation_example.rb +191 -0
  40. data/examples/advanced/mas/loop_workflow.rb +28 -0
  41. data/examples/advanced/mas/mock_planner.rb +77 -0
  42. data/examples/advanced/mas/proper_delegation_example.rb +276 -0
  43. data/examples/advanced/mcp/legate_mcp_server_resource_example.rb +182 -0
  44. data/examples/advanced/mcp/mcp_resource_server_example.rb +309 -0
  45. data/examples/advanced/mcp/mcp_server_async.rb +76 -0
  46. data/examples/advanced/mcp/mcp_server_async_tools.rb +122 -0
  47. data/examples/advanced/mcp/mcp_server_legate_agent.rb +95 -0
  48. data/examples/advanced/mcp/mcp_server_rack.rb +89 -0
  49. data/examples/advanced/random_calculator.rb +104 -0
  50. data/examples/advanced/sleep_agent.rb +153 -0
  51. data/examples/advanced/webhooks/webhook_e2e_runner.rb +110 -0
  52. data/examples/advanced/webhooks/webhook_receiver_agent.rb +58 -0
  53. data/examples/advanced/workflows/task_refinement_loop_agent.rb +278 -0
  54. data/examples/advanced/workflows/travel_planner_auto_sequential.rb +444 -0
  55. data/examples/advanced/workflows/travel_planner_parallel.rb +656 -0
  56. data/examples/advanced/workflows/travel_planner_sequential.rb +512 -0
  57. data/examples/tools/oauth2_example.rb +136 -0
  58. data/examples/tools/sleepy_tool.rb +42 -0
  59. data/lib/legate/activity_log.rb +71 -0
  60. data/lib/legate/agent.rb +959 -0
  61. data/lib/legate/agent_code_generator.rb +185 -0
  62. data/lib/legate/agent_definition.rb +812 -0
  63. data/lib/legate/agentic/decision.rb +49 -0
  64. data/lib/legate/agentic/loop.rb +134 -0
  65. data/lib/legate/agentic.rb +5 -0
  66. data/lib/legate/agents/loop_agent.rb +248 -0
  67. data/lib/legate/agents/parallel_agent.rb +163 -0
  68. data/lib/legate/agents/sequential_agent.rb +190 -0
  69. data/lib/legate/agents.rb +14 -0
  70. data/lib/legate/auth/config.rb +148 -0
  71. data/lib/legate/auth/coordinator.rb +218 -0
  72. data/lib/legate/auth/coordinators/oauth2_coordinator.rb +99 -0
  73. data/lib/legate/auth/coordinators/oidc_coordinator.rb +68 -0
  74. data/lib/legate/auth/coordinators/service_account_coordinator.rb +122 -0
  75. data/lib/legate/auth/credential.rb +157 -0
  76. data/lib/legate/auth/encryption.rb +108 -0
  77. data/lib/legate/auth/error.rb +94 -0
  78. data/lib/legate/auth/exchanged_credential.rb +180 -0
  79. data/lib/legate/auth/excon_middleware.rb +285 -0
  80. data/lib/legate/auth/http_client_utils.rb +364 -0
  81. data/lib/legate/auth/manager.rb +531 -0
  82. data/lib/legate/auth/manager_store.rb +394 -0
  83. data/lib/legate/auth/middleware_factory.rb +290 -0
  84. data/lib/legate/auth/runner.rb +279 -0
  85. data/lib/legate/auth/scheme.rb +125 -0
  86. data/lib/legate/auth/schemes/api_key.rb +212 -0
  87. data/lib/legate/auth/schemes/google_service_account.rb +108 -0
  88. data/lib/legate/auth/schemes/http_bearer.rb +98 -0
  89. data/lib/legate/auth/schemes/oauth2.rb +396 -0
  90. data/lib/legate/auth/schemes/openid_connect.rb +346 -0
  91. data/lib/legate/auth/schemes/service_account.rb +388 -0
  92. data/lib/legate/auth/schemes.rb +40 -0
  93. data/lib/legate/auth/token_manager.rb +362 -0
  94. data/lib/legate/auth/token_store.rb +86 -0
  95. data/lib/legate/auth/tool_context_extension.rb +97 -0
  96. data/lib/legate/auth/tool_integration.rb +188 -0
  97. data/lib/legate/auth/url_guard.rb +81 -0
  98. data/lib/legate/auth.rb +453 -0
  99. data/lib/legate/callbacks/callback_context.rb +71 -0
  100. data/lib/legate/cli/agent_commands.rb +950 -0
  101. data/lib/legate/cli/auth_commands.rb +520 -0
  102. data/lib/legate/cli/base_command.rb +24 -0
  103. data/lib/legate/cli/deployment_commands.rb +934 -0
  104. data/lib/legate/cli/output_helper.rb +108 -0
  105. data/lib/legate/cli/session_commands.rb +138 -0
  106. data/lib/legate/cli/skaffold_commands.rb +223 -0
  107. data/lib/legate/cli/tool_commands.rb +261 -0
  108. data/lib/legate/cli/web_commands.rb +182 -0
  109. data/lib/legate/cli.rb +40 -0
  110. data/lib/legate/configuration/webhooks.rb +113 -0
  111. data/lib/legate/configuration.rb +39 -0
  112. data/lib/legate/definition_store.rb +23 -0
  113. data/lib/legate/errors.rb +118 -0
  114. data/lib/legate/event.rb +161 -0
  115. data/lib/legate/gemini_ai_beta_patch.rb +39 -0
  116. data/lib/legate/generators/agent_generator.rb +412 -0
  117. data/lib/legate/generators/code_validator.rb +48 -0
  118. data/lib/legate/generators/legate/install_generator.rb +35 -0
  119. data/lib/legate/generators/legate/templates/create_legate_tables.rb.tt +36 -0
  120. data/lib/legate/generators/legate/templates/initializer.rb +18 -0
  121. data/lib/legate/generators/runtime_tool_loader.rb +76 -0
  122. data/lib/legate/generators/tool_generator.rb +408 -0
  123. data/lib/legate/generators.rb +11 -0
  124. data/lib/legate/global_definition_registry.rb +506 -0
  125. data/lib/legate/global_tool_manager.rb +135 -0
  126. data/lib/legate/llm/adapter.rb +69 -0
  127. data/lib/legate/llm/gemini.rb +172 -0
  128. data/lib/legate/llm/ollama.rb +80 -0
  129. data/lib/legate/llm.rb +34 -0
  130. data/lib/legate/mcp/client.rb +320 -0
  131. data/lib/legate/mcp/connection/sse.rb +292 -0
  132. data/lib/legate/mcp/connection/stdio.rb +273 -0
  133. data/lib/legate/mcp/connection_manager.rb +103 -0
  134. data/lib/legate/mcp/server/legate_agent_adapter.rb +170 -0
  135. data/lib/legate/mcp/server/legate_direct_agent_adapter.rb +140 -0
  136. data/lib/legate/mcp/server/legate_tool_adapter.rb +119 -0
  137. data/lib/legate/mcp/tool_wrapper.rb +138 -0
  138. data/lib/legate/mcp/util/schema_converter.rb +134 -0
  139. data/lib/legate/mcp.rb +23 -0
  140. data/lib/legate/plan_executor.rb +375 -0
  141. data/lib/legate/planner.rb +839 -0
  142. data/lib/legate/rails/railtie.rb +43 -0
  143. data/lib/legate/rails.rb +9 -0
  144. data/lib/legate/redaction.rb +32 -0
  145. data/lib/legate/session.rb +299 -0
  146. data/lib/legate/session_service/active_record.rb +300 -0
  147. data/lib/legate/session_service/base.rb +68 -0
  148. data/lib/legate/session_service/event_broadcast.rb +74 -0
  149. data/lib/legate/session_service/in_memory.rb +188 -0
  150. data/lib/legate/tool/metadata_dsl.rb +122 -0
  151. data/lib/legate/tool.rb +276 -0
  152. data/lib/legate/tool_code_generator.rb +103 -0
  153. data/lib/legate/tool_context.rb +350 -0
  154. data/lib/legate/tool_loader.rb +39 -0
  155. data/lib/legate/tool_registry.rb +73 -0
  156. data/lib/legate/tool_result.rb +61 -0
  157. data/lib/legate/tools/agent_tool.rb +187 -0
  158. data/lib/legate/tools/base/http_client.rb +319 -0
  159. data/lib/legate/tools/base/safe_url.rb +56 -0
  160. data/lib/legate/tools/base_async_job_tool.rb +91 -0
  161. data/lib/legate/tools/calculator.rb +89 -0
  162. data/lib/legate/tools/cat_facts.rb +81 -0
  163. data/lib/legate/tools/check_job_status_tool.rb +48 -0
  164. data/lib/legate/tools/current_time_tool.rb +64 -0
  165. data/lib/legate/tools/echo.rb +43 -0
  166. data/lib/legate/tools/http_request_tool.rb +105 -0
  167. data/lib/legate/tools/random_number_tool.rb +64 -0
  168. data/lib/legate/tools/read_webpage_tool.rb +92 -0
  169. data/lib/legate/tools/sleepy_tool.rb +74 -0
  170. data/lib/legate/tools/webhook_tool.rb +146 -0
  171. data/lib/legate/version.rb +5 -0
  172. data/lib/legate/web/app.rb +984 -0
  173. data/lib/legate/web/public/css/main.css +4980 -0
  174. data/lib/legate/web/public/images/favicon-256.png +0 -0
  175. data/lib/legate/web/public/images/favicon-32.png +0 -0
  176. data/lib/legate/web/public/images/legate-logo-dark.png +0 -0
  177. data/lib/legate/web/public/images/legate-logo-light.png +0 -0
  178. data/lib/legate/web/public/js/legate.js +616 -0
  179. data/lib/legate/web/public/styles/main.scss +4402 -0
  180. data/lib/legate/web/routes/agent_authentication_routes.rb +530 -0
  181. data/lib/legate/web/routes/agent_definition_routes.rb +803 -0
  182. data/lib/legate/web/routes/agent_generator_routes.rb +80 -0
  183. data/lib/legate/web/routes/agent_interaction_routes.rb +734 -0
  184. data/lib/legate/web/routes/agent_runtime_routes.rb +323 -0
  185. data/lib/legate/web/routes/api_routes.rb +56 -0
  186. data/lib/legate/web/routes/authentication_routes.rb +1541 -0
  187. data/lib/legate/web/routes/core_routes.rb +111 -0
  188. data/lib/legate/web/routes/documentation_routes.rb +220 -0
  189. data/lib/legate/web/routes/tool_generator_routes.rb +81 -0
  190. data/lib/legate/web/routes/tools_ui_routes.rb +207 -0
  191. data/lib/legate/web/sass_compiler.rb +73 -0
  192. data/lib/legate/web/views/_active_session_info.slim +25 -0
  193. data/lib/legate/web/views/_activity_list.slim +55 -0
  194. data/lib/legate/web/views/_agent_card.slim +56 -0
  195. data/lib/legate/web/views/_agent_generator_modal.slim +382 -0
  196. data/lib/legate/web/views/_agent_status_controls.slim +71 -0
  197. data/lib/legate/web/views/_agent_tool_table.slim +74 -0
  198. data/lib/legate/web/views/_chat_message.slim +95 -0
  199. data/lib/legate/web/views/_display_agent_configuration.slim +26 -0
  200. data/lib/legate/web/views/_display_agent_description.slim +11 -0
  201. data/lib/legate/web/views/_display_agent_fallback.slim +15 -0
  202. data/lib/legate/web/views/_display_agent_hierarchy.slim +93 -0
  203. data/lib/legate/web/views/_display_agent_instruction.slim +17 -0
  204. data/lib/legate/web/views/_display_agent_mcp.slim +13 -0
  205. data/lib/legate/web/views/_display_agent_model.slim +17 -0
  206. data/lib/legate/web/views/_display_agent_name.slim +42 -0
  207. data/lib/legate/web/views/_display_agent_output_key.slim +26 -0
  208. data/lib/legate/web/views/_display_agent_type.slim +65 -0
  209. data/lib/legate/web/views/_edit_agent_configuration.slim +74 -0
  210. data/lib/legate/web/views/_edit_agent_description.slim +16 -0
  211. data/lib/legate/web/views/_edit_agent_fallback.slim +25 -0
  212. data/lib/legate/web/views/_edit_agent_hierarchy.slim +98 -0
  213. data/lib/legate/web/views/_edit_agent_instruction.slim +49 -0
  214. data/lib/legate/web/views/_edit_agent_mcp.slim +33 -0
  215. data/lib/legate/web/views/_edit_agent_model.slim +23 -0
  216. data/lib/legate/web/views/_edit_agent_output_key.slim +36 -0
  217. data/lib/legate/web/views/_edit_agent_tools.slim +40 -0
  218. data/lib/legate/web/views/_edit_agent_type.slim +67 -0
  219. data/lib/legate/web/views/_session_error.slim +4 -0
  220. data/lib/legate/web/views/_skeleton.slim +69 -0
  221. data/lib/legate/web/views/_tool_card.slim +9 -0
  222. data/lib/legate/web/views/_tool_generator_modal.slim +311 -0
  223. data/lib/legate/web/views/agent.slim +436 -0
  224. data/lib/legate/web/views/agent_auth.slim +562 -0
  225. data/lib/legate/web/views/agents.slim +369 -0
  226. data/lib/legate/web/views/auth.slim +112 -0
  227. data/lib/legate/web/views/auth_credential_detail.slim +327 -0
  228. data/lib/legate/web/views/auth_credentials.slim +261 -0
  229. data/lib/legate/web/views/auth_debug.slim +94 -0
  230. data/lib/legate/web/views/auth_mapping_detail.slim +151 -0
  231. data/lib/legate/web/views/auth_mapping_new.slim +123 -0
  232. data/lib/legate/web/views/auth_mappings.slim +120 -0
  233. data/lib/legate/web/views/auth_scheme_detail.slim +274 -0
  234. data/lib/legate/web/views/auth_schemes.slim +259 -0
  235. data/lib/legate/web/views/auth_test.slim +418 -0
  236. data/lib/legate/web/views/chat.slim +192 -0
  237. data/lib/legate/web/views/docs_index.slim +105 -0
  238. data/lib/legate/web/views/docs_show.slim +105 -0
  239. data/lib/legate/web/views/error_404.slim +5 -0
  240. data/lib/legate/web/views/index.slim +148 -0
  241. data/lib/legate/web/views/layout.slim +144 -0
  242. data/lib/legate/web/views/tool_detail.slim +87 -0
  243. data/lib/legate/web/views/tools.slim +50 -0
  244. data/lib/legate/web/webhook_listener.rb +367 -0
  245. data/lib/legate/web.rb +9 -0
  246. data/lib/legate.rb +220 -0
  247. data/public/docs/advanced/callbacks.md +828 -0
  248. data/public/docs/advanced/mcp_schema_conversion.md +59 -0
  249. data/public/docs/authentication/api_reference/config.md +210 -0
  250. data/public/docs/authentication/api_reference/credential.md +246 -0
  251. data/public/docs/authentication/api_reference/encryption.md +218 -0
  252. data/public/docs/authentication/api_reference/exchanged_credential.md +271 -0
  253. data/public/docs/authentication/api_reference/excon_middleware.md +175 -0
  254. data/public/docs/authentication/api_reference/index.md +30 -0
  255. data/public/docs/authentication/api_reference/scheme.md +250 -0
  256. data/public/docs/authentication/api_reference/schemes/api_key.md +175 -0
  257. data/public/docs/authentication/api_reference/schemes/google_service_account.md +221 -0
  258. data/public/docs/authentication/api_reference/schemes/http_bearer.md +169 -0
  259. data/public/docs/authentication/api_reference/schemes/oauth2.md +343 -0
  260. data/public/docs/authentication/api_reference/schemes/oidc.md +73 -0
  261. data/public/docs/authentication/api_reference/schemes/openid_connect.md +311 -0
  262. data/public/docs/authentication/api_reference/schemes/service_account.md +287 -0
  263. data/public/docs/authentication/api_reference/token_manager.md +221 -0
  264. data/public/docs/authentication/api_reference/token_store.md +146 -0
  265. data/public/docs/authentication/api_reference/tool_context_extension.md +166 -0
  266. data/public/docs/authentication/guides/api_key.md +190 -0
  267. data/public/docs/authentication/guides/bearer.md +172 -0
  268. data/public/docs/authentication/guides/configuration.md +255 -0
  269. data/public/docs/authentication/guides/custom_flow.md +523 -0
  270. data/public/docs/authentication/guides/index.md +24 -0
  271. data/public/docs/authentication/guides/migration.md +435 -0
  272. data/public/docs/authentication/guides/oauth2.md +252 -0
  273. data/public/docs/authentication/guides/oidc.md +241 -0
  274. data/public/docs/authentication/guides/overview.md +155 -0
  275. data/public/docs/authentication/guides/secure_storage.md +301 -0
  276. data/public/docs/authentication/guides/service_account.md +228 -0
  277. data/public/docs/authentication/guides/token_lifecycle.md +295 -0
  278. data/public/docs/authentication/guides/web_ui_integration.md +504 -0
  279. data/public/docs/authentication/index.md +58 -0
  280. data/public/docs/authentication/troubleshooting/credential_storage.md +550 -0
  281. data/public/docs/authentication/troubleshooting/environment_variables.md +540 -0
  282. data/public/docs/authentication/troubleshooting/index.md +11 -0
  283. data/public/docs/authentication/troubleshooting/oauth2_issues.md +220 -0
  284. data/public/docs/authentication/troubleshooting/oidc_issues.md +412 -0
  285. data/public/docs/authentication/troubleshooting/token_refresh.md +338 -0
  286. data/public/docs/cli/legate_cli_usage.md +363 -0
  287. data/public/docs/core_concepts/legate_agent_lifecycle.md +124 -0
  288. data/public/docs/core_concepts/legate_architecture_overview.md +110 -0
  289. data/public/docs/core_concepts/legate_configuration.md +116 -0
  290. data/public/docs/core_concepts/legate_definition_store.md +102 -0
  291. data/public/docs/core_concepts/legate_planner.md +94 -0
  292. data/public/docs/core_concepts/legate_session_service.md +104 -0
  293. data/public/docs/error_handling/legate_error_handling.md +122 -0
  294. data/public/docs/examples.md +199 -0
  295. data/public/docs/getting_started.md +111 -0
  296. data/public/docs/guides/agentic_agents.md +137 -0
  297. data/public/docs/guides/ai_code_generators.md +437 -0
  298. data/public/docs/guides/auto_loading.md +326 -0
  299. data/public/docs/guides/configuring_agent_webhooks.md +219 -0
  300. data/public/docs/guides/http_client_usage.md +264 -0
  301. data/public/docs/guides/llm_providers.md +137 -0
  302. data/public/docs/guides/mcp_client_integration.md +232 -0
  303. data/public/docs/guides/mcp_server_exposure.md +206 -0
  304. data/public/docs/guides/rails_integration.md +128 -0
  305. data/public/docs/guides/sending_outbound_webhooks.md +227 -0
  306. data/public/docs/guides/streaming.md +112 -0
  307. data/public/docs/guides/webhooks.md +288 -0
  308. data/public/docs/introduction.md +51 -0
  309. data/public/docs/multi_agent_systems/advanced_features.md +57 -0
  310. data/public/docs/multi_agent_systems/agent_delegation.md +190 -0
  311. data/public/docs/multi_agent_systems/agent_hierarchy.md +49 -0
  312. data/public/docs/multi_agent_systems/state_management.md +47 -0
  313. data/public/docs/multi_agent_systems/workflow_agents.md +72 -0
  314. data/public/docs/tools/legate_built_in_tools.md +332 -0
  315. data/public/docs/tools/legate_tools_and_registry.md +263 -0
  316. data/public/docs/web_ui/legate_web_ui.md +137 -0
  317. metadata +823 -0
@@ -0,0 +1,435 @@
1
+ # Migrating from Earlier Versions
2
+
3
+ ## Overview
4
+
5
+ This guide helps you migrate your Legate Ruby applications from earlier versions to the current version, focusing on authentication-related changes.
6
+
7
+ ## Migrating from v0.4.x
8
+
9
+ ### Breaking Changes
10
+
11
+ Version 0.5.0 introduced a new authentication system that replaces the previous authentication mechanisms. The following breaking changes were introduced:
12
+
13
+ 1. **Unified Authentication System**: Replaced multiple authentication strategies with a single unified system
14
+ 2. **Authentication Schemes**: Introduced the concept of authentication schemes to define how APIs expect credentials
15
+ 3. **Token Management**: Added comprehensive token lifecycle management
16
+ 4. **Interactive Authentication**: Implemented Fiber-based control flow for interactive authentication
17
+
18
+ ### Migration Steps
19
+
20
+ #### Step 1: Update Dependencies
21
+
22
+ ```ruby
23
+ # Old (v0.4.x)
24
+ gem 'legate', '~> 0.4.0'
25
+
26
+ # New (v0.5.x)
27
+ gem 'legate', '~> 0.5.0'
28
+ ```
29
+
30
+ #### Step 2: Migrate API Key Authentication
31
+
32
+ ```ruby
33
+ # Old (v0.4.x)
34
+ toolset = Legate::Tool::OpenAPIToolset.new(
35
+ spec_path: 'path/to/openapi_spec.json',
36
+ api_key: ENV['API_KEY']
37
+ )
38
+
39
+ # New (v0.5.x)
40
+ # ApiKey.new takes no arguments; location/name live on the credential
41
+ api_key_scheme = Legate::Auth::Schemes::ApiKey.new
42
+
43
+ api_key_credential = Legate::Auth::Credential.new(
44
+ auth_type: :api_key,
45
+ api_key: ENV['API_KEY'],
46
+ location: 'header',
47
+ name: 'X-API-Key'
48
+ )
49
+
50
+ # Attach the scheme/credential to an outbound connection
51
+ connection = Legate::Auth.create_connection('https://api.example.com',
52
+ scheme: api_key_scheme,
53
+ credential: api_key_credential
54
+ )
55
+ ```
56
+
57
+ #### Step 3: Migrate OAuth2 Authentication
58
+
59
+ ```ruby
60
+ # Old (v0.4.x)
61
+ toolset = Legate::Tool::OpenAPIToolset.new(
62
+ spec_path: 'path/to/openapi_spec.json',
63
+ oauth2_client_id: ENV['OAUTH2_CLIENT_ID'],
64
+ oauth2_client_secret: ENV['OAUTH2_CLIENT_SECRET'],
65
+ oauth2_auth_url: 'https://auth.example.com/authorize',
66
+ oauth2_token_url: 'https://auth.example.com/token',
67
+ oauth2_scopes: ['read', 'write']
68
+ )
69
+
70
+ # New (v0.5.x) — note: authorization_url (not auth_url)
71
+ oauth2_scheme = Legate::Auth::Schemes::OAuth2.new(
72
+ authorization_url: 'https://auth.example.com/authorize',
73
+ token_url: 'https://auth.example.com/token',
74
+ scopes: ['read', 'write']
75
+ )
76
+
77
+ oauth2_credential = Legate::Auth::Credential.new(
78
+ auth_type: :oauth2,
79
+ client_id: ENV['OAUTH2_CLIENT_ID'],
80
+ client_secret: ENV['OAUTH2_CLIENT_SECRET']
81
+ )
82
+
83
+ # Drive the flow via Config (build_authorization_uri / exchange_token)
84
+ config = Legate::Auth::Config.new(scheme: oauth2_scheme, credential: oauth2_credential)
85
+ ```
86
+
87
+ #### Step 4: Migrate Custom Function Tools
88
+
89
+ ```ruby
90
+ # Old (v0.4.x)
91
+ class MyApiTool < Legate::Tool::FunctionTool
92
+ def call(context, **params)
93
+ api_key = ENV['API_KEY']
94
+ response = Excon.get(
95
+ 'https://api.example.com/data',
96
+ headers: { 'X-API-Key' => api_key }
97
+ )
98
+ JSON.parse(response.body)
99
+ end
100
+ end
101
+
102
+ # New (v0.5.x)
103
+ class MyApiTool < Legate::Tool
104
+ tool_description 'A tool that interacts with an API'
105
+
106
+ def perform_execution(params, context)
107
+ api_key_scheme = Legate::Auth::Schemes::ApiKey.new
108
+ api_key_credential = Legate::Auth::Credential.new(
109
+ auth_type: :api_key,
110
+ api_key: ENV['API_KEY'],
111
+ location: 'header',
112
+ name: 'X-API-Key'
113
+ )
114
+
115
+ connection = Legate::Auth.create_connection(
116
+ 'https://api.example.com',
117
+ scheme: api_key_scheme,
118
+ credential: api_key_credential
119
+ )
120
+
121
+ response = connection.get(path: '/data')
122
+ { status: :success, result: JSON.parse(response.body) }
123
+ end
124
+ end
125
+ ```
126
+
127
+ #### Step 5: Migrate Interactive Authentication Flows
128
+
129
+ ```ruby
130
+ # Old (v0.4.x)
131
+ def call(context, **params)
132
+ oauth2_client = OAuth2::Client.new(
133
+ ENV['OAUTH2_CLIENT_ID'],
134
+ ENV['OAUTH2_CLIENT_SECRET'],
135
+ site: 'https://auth.example.com',
136
+ authorize_url: '/authorize',
137
+ token_url: '/token'
138
+ )
139
+
140
+ if context.session[:oauth2_access_token]
141
+ # Use cached token
142
+ access_token = OAuth2::AccessToken.from_hash(
143
+ oauth2_client,
144
+ JSON.parse(context.session[:oauth2_access_token])
145
+ )
146
+ else
147
+ # Request authorization
148
+ auth_url = oauth2_client.auth_code.authorize_url(
149
+ redirect_uri: 'http://localhost:8080/callback',
150
+ scope: 'read write'
151
+ )
152
+
153
+ # Yield for user interaction
154
+ auth_code = yield(auth_url)
155
+
156
+ # Exchange code for token
157
+ access_token = oauth2_client.auth_code.get_token(
158
+ auth_code,
159
+ redirect_uri: 'http://localhost:8080/callback'
160
+ )
161
+
162
+ # Cache the token
163
+ context.session[:oauth2_access_token] = access_token.to_hash.to_json
164
+ end
165
+
166
+ # Use the token
167
+ response = access_token.get('/api/data')
168
+ JSON.parse(response.body)
169
+ end
170
+
171
+ # New (v0.5.x)
172
+ # Interactive flows are driven by Config (build the auth URI, then exchange the
173
+ # code) or, inside a tool, via context.with_authentication which yields the auth
174
+ # request to your application. A direct Config-based example:
175
+ def perform_execution(params, context)
176
+ scheme = Legate::Auth::Schemes::OAuth2.new(
177
+ authorization_url: 'https://auth.example.com/authorize',
178
+ token_url: 'https://auth.example.com/token',
179
+ scopes: %w[read write]
180
+ )
181
+ credential = Legate::Auth::Credential.new(
182
+ auth_type: :oauth2,
183
+ client_id: ENV['OAUTH2_CLIENT_ID'],
184
+ client_secret: ENV['OAUTH2_CLIENT_SECRET']
185
+ )
186
+
187
+ config = Legate::Auth::Config.new(scheme: scheme, credential: credential)
188
+ state = SecureRandom.hex(16)
189
+ auth_uri = config.build_authorization_uri('http://localhost:8080/callback', state)
190
+
191
+ # Yield the auth URI to the caller for the user to complete, then on the
192
+ # callback set config.response_uri and exchange:
193
+ # config.response_uri = '...callback?code=...&state=...'
194
+ # token = scheme.exchange_token(config, credential)
195
+
196
+ { status: :success, result: { auth_uri: auth_uri } }
197
+ end
198
+ ```
199
+
200
+ #### Step 6: Migrate Session Storage
201
+
202
+ ```ruby
203
+ # Sessions are now always in-memory (Redis session service has been removed)
204
+ session_service = Legate::SessionService::InMemory.new
205
+
206
+ # Create a token store for authentication (positional argument)
207
+ token_store = Legate::Auth::TokenStore.new(session_service)
208
+
209
+ # Create a token manager (positional token store; optional positional config Hash).
210
+ # The TokenManager does not take a scheme — the scheme is passed per call to
211
+ # get_token(scheme, credential).
212
+ token_manager = Legate::Auth::TokenManager.new(token_store)
213
+ ```
214
+
215
+ ## Migrating from v0.3.x
216
+
217
+ ### Major Changes from v0.3.x to v0.5.x
218
+
219
+ Version 0.3.x had limited authentication support, using direct API key or OAuth2 configuration. Version 0.5.x introduces a comprehensive authentication system with the following improvements:
220
+
221
+ 1. **Unified Authentication**: A single authentication system for all authentication methods
222
+ 2. **Improved Security**: Scoped token storage, optional opt-in at-rest encryption, and SSRF-guarded auth URLs
223
+ 3. **Interactive Flows**: Support for OAuth2, OIDC, and custom interactive flows
224
+ 4. **Token Lifecycle**: Automatic token refresh and invalidation
225
+ 5. **Middleware Support**: Excon middleware for authentication
226
+
227
+ ### Migration Steps
228
+
229
+ #### Step 1: Update Dependencies
230
+
231
+ ```ruby
232
+ # Old (v0.3.x)
233
+ gem 'legate', '~> 0.3.0'
234
+
235
+ # New (v0.5.x)
236
+ gem 'legate', '~> 0.5.0'
237
+ ```
238
+
239
+ #### Step 2: Migrate Basic Authentication
240
+
241
+ ```ruby
242
+ # Old (v0.3.x)
243
+ toolset = Legate::Tool::OpenAPIToolset.new(
244
+ spec_path: 'path/to/openapi_spec.json',
245
+ auth: {
246
+ type: 'basic',
247
+ username: ENV['USERNAME'],
248
+ password: ENV['PASSWORD']
249
+ }
250
+ )
251
+
252
+ # New (v0.5.x)
253
+ # For Basic Auth, use HTTP Bearer with Basic encoded token
254
+ require 'base64'
255
+ basic_token = Base64.strict_encode64("#{ENV['USERNAME']}:#{ENV['PASSWORD']}")
256
+
257
+ bearer_scheme = Legate::Auth::Schemes::HTTPBearer.new
258
+
259
+ bearer_credential = Legate::Auth::Credential.new(
260
+ auth_type: :http_bearer,
261
+ bearer_token: "Basic #{basic_token}"
262
+ )
263
+
264
+ connection = Legate::Auth.create_connection('https://api.example.com',
265
+ scheme: bearer_scheme,
266
+ credential: bearer_credential
267
+ )
268
+ ```
269
+
270
+ #### Step 3: Migrate API Key Authentication
271
+
272
+ ```ruby
273
+ # Old (v0.3.x)
274
+ toolset = Legate::Tool::OpenAPIToolset.new(
275
+ spec_path: 'path/to/openapi_spec.json',
276
+ auth: {
277
+ type: 'api_key',
278
+ in: 'header',
279
+ name: 'X-API-Key',
280
+ value: ENV['API_KEY']
281
+ }
282
+ )
283
+
284
+ # New (v0.5.x) — ApiKey.new takes no args; location/name on the credential
285
+ api_key_scheme = Legate::Auth::Schemes::ApiKey.new
286
+
287
+ api_key_credential = Legate::Auth::Credential.new(
288
+ auth_type: :api_key,
289
+ api_key: ENV['API_KEY'],
290
+ location: 'header',
291
+ name: 'X-API-Key'
292
+ )
293
+
294
+ connection = Legate::Auth.create_connection('https://api.example.com',
295
+ scheme: api_key_scheme,
296
+ credential: api_key_credential
297
+ )
298
+ ```
299
+
300
+ ## Additional Migration Notes
301
+
302
+ ### Environment Variables
303
+
304
+ The new authentication system encourages the use of environment variables for credential values:
305
+
306
+ ```ruby
307
+ # Reference environment variables with the ENV: prefix (no *_env attributes)
308
+ oauth2_credential = Legate::Auth::Credential.new(
309
+ auth_type: :oauth2,
310
+ client_id: 'ENV:OAUTH2_CLIENT_ID',
311
+ client_secret: 'ENV:OAUTH2_CLIENT_SECRET'
312
+ )
313
+ ```
314
+
315
+ ### Web UI Integration
316
+
317
+ If you're using the Legate Web UI, you'll need to update your authentication integration:
318
+
319
+ ```ruby
320
+ # Old (v0.4.x)
321
+ get '/auth/callback' do
322
+ code = params[:code]
323
+ session[:auth_code] = code
324
+ # Close popup window
325
+ '<script>window.close();</script>'
326
+ end
327
+
328
+ # New (v0.5.x)
329
+ get '/auth/callback' do
330
+ # Verify state parameter
331
+ client_state = params[:state]
332
+ server_state = session[:oauth2_state]
333
+ halt 403, 'Invalid state parameter' unless client_state && client_state == server_state
334
+
335
+ # Get the authorization code
336
+ code = params[:code]
337
+ halt 400, 'Missing authorization code' unless code
338
+
339
+ # Store the authorization code and response URI
340
+ session[:auth_code] = code
341
+ session[:auth_response_uri] = request.url
342
+
343
+ # Close the popup window and notify the parent window
344
+ <<-HTML
345
+ <script>
346
+ window.opener.postMessage({ type: 'auth_callback', code: '#{code}' }, window.location.origin);
347
+ window.close();
348
+ </script>
349
+ HTML
350
+ end
351
+ ```
352
+
353
+ ### Helper Migration
354
+
355
+ If you've created custom helpers for authentication, you'll need to update them:
356
+
357
+ ```ruby
358
+ # Old (v0.4.x)
359
+ def authenticate_api_request(api_key)
360
+ headers = { 'X-API-Key' => api_key }
361
+ # ...
362
+ end
363
+
364
+ # New (v0.5.x)
365
+ def authenticate_api_request(scheme, credential)
366
+ connection = Legate::Auth.create_connection(
367
+ 'https://api.example.com',
368
+ scheme: scheme,
369
+ credential: credential
370
+ )
371
+ # ...
372
+ end
373
+ ```
374
+
375
+ ## Troubleshooting Migration Issues
376
+
377
+ ### Missing Authentication Scheme
378
+
379
+ If a scheme is misconfigured, validation raises a `Legate::Auth::SchemeValidationError`
380
+ (a subclass of `ConfigurationError`). Note the auth exception classes are flat under
381
+ `Legate::Auth` (e.g. `SchemeValidationError`, `CredentialError`, `TokenRefreshError`) —
382
+ there is no `Legate::Auth::Error::*` sub-namespace.
383
+
384
+ ```
385
+ Legate::Auth::SchemeValidationError: Invalid authentication scheme configuration
386
+ ```
387
+
388
+ Make sure you've created the scheme with the required arguments:
389
+
390
+ ```ruby
391
+ # ApiKey.new takes no arguments; location/name live on the credential
392
+ api_key_scheme = Legate::Auth::Schemes::ApiKey.new
393
+ ```
394
+
395
+ ### Missing or Invalid Credential
396
+
397
+ A missing required attribute (or an invalid `auth_type`) raises
398
+ `Legate::Auth::CredentialError`:
399
+
400
+ ```
401
+ Legate::Auth::CredentialError: Missing required attributes for api_key: api_key
402
+ ```
403
+
404
+ Make sure you've created the credential with `auth_type:` and the required attributes:
405
+
406
+ ```ruby
407
+ api_key_credential = Legate::Auth::Credential.new(
408
+ auth_type: :api_key,
409
+ api_key: ENV['API_KEY']
410
+ )
411
+ ```
412
+
413
+ ### Token Refresh Failures
414
+
415
+ A failed refresh raises `Legate::Auth::TokenRefreshError`:
416
+
417
+ ```
418
+ Legate::Auth::TokenRefreshError: Token refresh failed
419
+ ```
420
+
421
+ Make sure you've configured the token store and manager correctly (positional args):
422
+
423
+ ```ruby
424
+ # Create a token store and manager (positional arguments)
425
+ token_store = Legate::Auth::TokenStore.new(session_service)
426
+ token_manager = Legate::Auth::TokenManager.new(token_store)
427
+ ```
428
+
429
+ ## Related Topics
430
+ - [Authentication Configuration](./configuration)
431
+ - [Token Lifecycle Management](./token_lifecycle)
432
+ - [Secure Credential Storage](./secure_storage)
433
+ - [OAuth2 Authentication](./oauth2)
434
+ - [OpenID Connect](./oidc)
435
+ - [Service Account Authentication](./service_account)
@@ -0,0 +1,252 @@
1
+ # OAuth2 Authentication
2
+
3
+ OAuth2 is a powerful authentication framework that allows users to authorize applications to access their accounts on other services without sharing their credentials. The Legate Ruby library provides comprehensive support for OAuth2 authentication flows.
4
+
5
+ ## Overview
6
+
7
+ OAuth2 is an industry-standard protocol for authorization that enables secure API access without sharing passwords. The Legate Ruby library supports the following OAuth2 flows:
8
+
9
+ - **Authorization Code Flow**: The most common flow for web applications, involving a browser-based user consent
10
+ - **Client Credentials Flow**: Used for server-to-server authentication
11
+ - **Password Flow**: Used for trusted applications that can collect user credentials directly
12
+
13
+ ## Configuration
14
+
15
+ ### Creating an OAuth2 Scheme
16
+
17
+ ```ruby
18
+ # Basic OAuth2 scheme with required parameters
19
+ scheme = Legate::Auth::Schemes::OAuth2.new(
20
+ authorization_url: 'https://auth.example.com/authorize',
21
+ token_url: 'https://auth.example.com/token',
22
+ scopes: ['profile', 'email', 'data:read']
23
+ )
24
+
25
+ # OAuth2 scheme with additional options
26
+ scheme = Legate::Auth::Schemes::OAuth2.new(
27
+ authorization_url: 'https://auth.example.com/authorize',
28
+ token_url: 'https://auth.example.com/token',
29
+ scopes: ['profile', 'email', 'data:read'],
30
+ use_pkce: true, # Use PKCE for enhanced security (default: true)
31
+ additional_params: {
32
+ 'access_type' => 'offline', # Request a refresh token
33
+ 'prompt' => 'consent' # Force the consent screen to appear
34
+ },
35
+ revocation_url: 'https://auth.example.com/revoke' # For token revocation
36
+ )
37
+ ```
38
+
39
+ ### Creating an OAuth2 Credential
40
+
41
+ ```ruby
42
+ # Basic OAuth2 credential with client ID and secret
43
+ credential = Legate::Auth::Credential.new(
44
+ auth_type: :oauth2,
45
+ client_id: ENV['CLIENT_ID'],
46
+ client_secret: ENV['CLIENT_SECRET']
47
+ )
48
+
49
+ # With additional options
50
+ credential = Legate::Auth::Credential.new(
51
+ auth_type: :oauth2,
52
+ client_id: ENV['CLIENT_ID'],
53
+ client_secret: ENV['CLIENT_SECRET'],
54
+ additional_params: {
55
+ 'client_authentication' => 'body' # Send client credentials in request body
56
+ }
57
+ )
58
+ ```
59
+
60
+ ## Authentication Flows
61
+
62
+ ### Authorization Code Flow
63
+
64
+ The authorization code flow is an interactive flow that requires user authentication and consent:
65
+
66
+ ```ruby
67
+ # 1. Configure the OAuth2 scheme and credential
68
+ scheme = Legate::Auth::Schemes::OAuth2.new(
69
+ authorization_url: 'https://auth.example.com/authorize',
70
+ token_url: 'https://auth.example.com/token',
71
+ scopes: ['profile', 'email']
72
+ )
73
+
74
+ credential = Legate::Auth::Credential.new(
75
+ auth_type: :oauth2,
76
+ client_id: ENV['CLIENT_ID'],
77
+ client_secret: ENV['CLIENT_SECRET']
78
+ )
79
+
80
+ # 2. Build the authorization URI via a Config and redirect the user
81
+ config = Legate::Auth::Config.new(scheme: scheme, credential: credential)
82
+ state = SecureRandom.hex(16)
83
+ auth_url = config.build_authorization_uri('https://your-app.com/callback', state)
84
+
85
+ # 3. Redirect the user to auth_url
86
+ # redirect_to auth_url
87
+ # The user authenticates and is redirected back to your callback URL
88
+
89
+ # 4. On the callback, set the response URI on the config
90
+ config.response_uri = 'https://your-app.com/callback?code=12345&state=abcde'
91
+
92
+ # 5. Exchange the authorization code for tokens (state is verified internally)
93
+ token = scheme.exchange_token(config, credential)
94
+ puts token[:access_token]
95
+ ```
96
+
97
+ > This shows the direct `Config`/scheme flow. Tools can also drive OAuth2
98
+ > interactively via `context.with_authentication` (see
99
+ > [`ToolContextExtension`](../api_reference/tool_context_extension)), which
100
+ > yields the auth request to your application for handling.
101
+
102
+ ### Client Credentials Flow
103
+
104
+ The client credentials flow is a non-interactive flow used for server-to-server authentication:
105
+
106
+ ```ruby
107
+ # 1. Configure the OAuth2 scheme
108
+ scheme = Legate::Auth::Schemes::OAuth2.new(
109
+ token_url: 'https://auth.example.com/token',
110
+ scopes: ['api:access']
111
+ )
112
+
113
+ # 2. Configure the credential
114
+ credential = Legate::Auth::Credential.new(
115
+ auth_type: :oauth2,
116
+ client_id: ENV['CLIENT_ID'],
117
+ client_secret: ENV['CLIENT_SECRET']
118
+ )
119
+
120
+ # 3. Request a token directly using the client credentials grant
121
+ token = scheme.client_credentials_token(credential)
122
+
123
+ # 4. Apply the token to outbound requests
124
+ connection = Legate::Auth.create_connection('https://api.example.com',
125
+ scheme: scheme,
126
+ credential: token
127
+ )
128
+ result = connection.get(path: '/resource')
129
+ ```
130
+
131
+ ## Token Refresh
132
+
133
+ OAuth2 access tokens typically expire after a short period. When you use a `TokenManager`, it automatically refreshes refreshable tokens before they expire:
134
+
135
+ ```ruby
136
+ # Configure the scheme with a scope that returns a refresh token
137
+ scheme = Legate::Auth::Schemes::OAuth2.new(
138
+ authorization_url: 'https://auth.example.com/authorize',
139
+ token_url: 'https://auth.example.com/token',
140
+ scopes: ['profile', 'email', 'offline_access'], # offline_access requests a refresh token
141
+ additional_params: {
142
+ 'access_type' => 'offline' # For Google OAuth2, request a refresh token
143
+ }
144
+ )
145
+
146
+ # A TokenManager will auto-refresh the cached token when it is near expiry
147
+ token_store = Legate::Auth::TokenStore.new(session_service)
148
+ token_manager = Legate::Auth::TokenManager.new(token_store)
149
+ token = token_manager.get_token(scheme, credential)
150
+ ```
151
+
152
+ ## Advanced Features
153
+
154
+ ### PKCE (Proof Key for Code Exchange)
155
+
156
+ PKCE enhances security for public clients by preventing authorization code interception attacks:
157
+
158
+ ```ruby
159
+ # PKCE is enabled by default
160
+ scheme = Legate::Auth::Schemes::OAuth2.new(
161
+ authorization_url: 'https://auth.example.com/authorize',
162
+ token_url: 'https://auth.example.com/token',
163
+ scopes: ['profile', 'email'],
164
+ use_pkce: true # This is the default
165
+ )
166
+ ```
167
+
168
+ ### Token Revocation
169
+
170
+ When you no longer need an access token, you can revoke it:
171
+
172
+ ```ruby
173
+ # Configure the scheme with a revocation URL
174
+ scheme = Legate::Auth::Schemes::OAuth2.new(
175
+ authorization_url: 'https://auth.example.com/authorize',
176
+ token_url: 'https://auth.example.com/token',
177
+ revocation_url: 'https://auth.example.com/revoke',
178
+ scopes: ['profile', 'email']
179
+ )
180
+
181
+ # Revoke a token
182
+ token_manager = Legate::Auth::TokenManager.new
183
+ token_manager.revoke_token(token, credential)
184
+ ```
185
+
186
+ ## Provider-Specific Configurations
187
+
188
+ ### Google OAuth2
189
+
190
+ ```ruby
191
+ scheme = Legate::Auth::Schemes::OAuth2.new(
192
+ authorization_url: 'https://accounts.google.com/o/oauth2/auth',
193
+ token_url: 'https://oauth2.googleapis.com/token',
194
+ scopes: ['https://www.googleapis.com/auth/userinfo.email',
195
+ 'https://www.googleapis.com/auth/userinfo.profile'],
196
+ additional_params: {
197
+ 'access_type' => 'offline',
198
+ 'prompt' => 'consent' # Force consent screen to appear
199
+ }
200
+ )
201
+ ```
202
+
203
+ ### GitHub OAuth2
204
+
205
+ ```ruby
206
+ scheme = Legate::Auth::Schemes::OAuth2.new(
207
+ authorization_url: 'https://github.com/login/oauth/authorize',
208
+ token_url: 'https://github.com/login/oauth/access_token',
209
+ scopes: ['user', 'repo']
210
+ )
211
+ ```
212
+
213
+ ### Microsoft Azure OAuth2
214
+
215
+ ```ruby
216
+ tenant_id = 'common' # Use 'common' for multi-tenant, or a specific tenant ID
217
+
218
+ scheme = Legate::Auth::Schemes::OAuth2.new(
219
+ authorization_url: "https://login.microsoftonline.com/#{tenant_id}/oauth2/v2.0/authorize",
220
+ token_url: "https://login.microsoftonline.com/#{tenant_id}/oauth2/v2.0/token",
221
+ scopes: ['user.read', 'offline_access']
222
+ )
223
+ ```
224
+
225
+ ## Security Considerations
226
+
227
+ - **Client Secret Protection**: Always store client secrets securely, preferably in environment variables.
228
+ - **HTTPS**: Ensure all OAuth2 endpoints use HTTPS to protect token transmission.
229
+ - **Use PKCE**: Enable PKCE (enabled by default) for enhanced security, especially for mobile or desktop applications.
230
+ - **Scoped Tokens**: Request only the scopes your application needs.
231
+ - **Token Storage**: Tokens are cached in scoped session state as plaintext. For at-rest encryption, apply the opt-in `Legate::Auth::Encryption` module yourself.
232
+
233
+ ## Troubleshooting
234
+
235
+ If you encounter issues with OAuth2 authentication:
236
+
237
+ - Check that all URLs and credentials are correct
238
+ - Verify that redirect URIs exactly match those registered with the provider
239
+ - Ensure scopes are properly formatted and allowed by the provider
240
+ - See the [OAuth2 Troubleshooting Guide](../troubleshooting/oauth2_issues) for detailed solutions
241
+
242
+ ## Related Topics
243
+
244
+ - [OpenID Connect](./oidc) - Learn about OpenID Connect, an identity layer built on top of OAuth2
245
+ - [Token Lifecycle Management](./token_lifecycle) - Advanced token management techniques
246
+ - [Secure Credential Storage](./secure_storage) - Best practices for credential security
247
+
248
+ ## Next Steps
249
+
250
+ - [OpenID Connect](./oidc): Learn about OpenID Connect, an identity layer built on top of OAuth2
251
+ - [Token Lifecycle Management](./token_lifecycle): Advanced token management techniques
252
+ - [Secure Credential Storage](./secure_storage): Best practices for credential security