npx skills add https://github.com/incept5/eve-skillpacks --skill eve-auth-and-secrets使用此工作流程登录 Eve 并管理应用的密钥。
eve auth login
eve auth login --ttl 30 # 自定义令牌 TTL(1-90 天)
eve auth status
Eve 使用挑战-响应认证。默认提供商是 github_ssh:
| 类型 | 签发方式 | 使用场景 |
|---|---|---|
| 用户令牌 | eve auth login |
Use this workflow to log in to Eve and manage secrets for your app.
eve auth login
eve auth login --ttl 30 # custom token TTL (1-90 days)
eve auth status
Eve uses challenge-response authentication. The default provider is github_ssh:
| Type | Issued Via | Use Case |
|---|
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| 交互式 CLI 会话 |
| 作业令牌 | Worker 自动签发 | 作业内的代理执行 |
| 铸造令牌 | eve auth mint | 机器人/服务账户 |
JWT 载荷包含 sub(用户 ID)、org_id、scope 和 exp。通过 JWKS 端点验证令牌:GET /auth/jwks。
角色和组织成员资格的更改立即生效——服务器从实时数据库成员资格解析权限,而非过时的 JWT 声明。当请求包含 project_id 但不包含 org_id 时,权限守卫会从项目所属组织派生组织上下文。
检查当前令牌可以执行的操作:
eve auth permissions
为多提供商访问注册额外的身份:
curl -X POST "$EVE_API_URL/auth/identities" -H "Authorization: Bearer $TOKEN" \
-d '{"provider": "nostr", "external_id": "<pubkey>"}'
Eve 支持可插拔的身份提供商。认证守卫首先尝试 Bearer JWT,然后是特定于提供商的请求认证。
| 提供商 | 认证方法 | 使用场景 |
|---|---|---|
github_ssh | SSH 挑战-响应 | 默认 CLI 登录 |
nostr | NIP-98 请求认证 + 挑战-响应 | Nostr 原生用户 |
两种路径:
eve auth login --provider nostr。通过 CLI 或 API 邀请外部用户:
# 通过 SSH 密钥注册邀请(注册密钥以便用户可立即登录)
eve admin invite --email user@example.com --ssh-key ~/.ssh/id_ed25519.pub --org org_xxx
# 通过 GitHub 身份邀请
eve admin invite --email user@example.com --github ghuser --org org_xxx
# 通过基于 Web 的认证(Supabase)邀请
eve admin invite --email user@example.com --web --org org_xxx
# API:针对 Nostr 公钥的邀请
curl -X POST "$EVE_API_URL/auth/invites" -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"org_id": "org_xxx", "role": "member", "provider_hint": "nostr", "identity_hint": "<pubkey>"}'
如果未指定认证方法(--github、--ssh-key 或 --web),CLI 会警告用户将无法登录。用户稍后可以通过 eve auth request-access --org "Org Name" --ssh-key ~/.ssh/id_ed25519.pub --wait 自行注册。
当身份通过认证时,Eve 会自动配置其账户和组织成员资格。
对于应用驱动的入职流程,请使用组织作用域的邀请 API,而不是传统的管理员邀请流程:
# 创建一个带有应用返回 URL 的组织作用域 Supabase 邀请
curl -X POST "$EVE_API_URL/orgs/org_xxx/invites" \
-H "Authorization: Bearer $USER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"role": "member",
"redirect_to": "https://app.example.com/invite/complete",
"app_context": { "project_id": "proj_123" }
}'
# 搜索现有组织成员以用于分配者选择器
curl "$EVE_API_URL/orgs/org_xxx/members/search?q=ali" \
-H "Authorization: Bearer $USER_TOKEN"
使用具有 orgs:invite 权限的用户令牌来创建或列出这些邀请,并使用 orgs:members:read 进行成员查找。邀请邮件应发送到 GoTrue 的 /verify 路径,而不是直接发送到 OAuth 回调。如果邀请在 SSO 交换期间自动应用,Eve 会返回 invite_redirect_to,以便即使邮件提供商剥离了嵌套的重定向参数,SSO 回调也能将用户送回目标应用。当前的邀请入职流程首先建立 SSO 会话,然后在重定向到应用之前将用户引导至 /set-password。
为无需 SSH 登录的机器人/服务用户铸造令牌:
# 为机器人用户铸造令牌(如果需要,创建用户 + 成员资格)
eve auth mint --email app-bot@example.com --org org_xxx
# 使用自定义 TTL(1-90 天,默认值:服务器配置)
eve auth mint --email app-bot@example.com --org org_xxx --ttl 90
# 将作用域限定到具有管理员角色的项目
eve auth mint --email app-bot@example.com --project proj_xxx --role admin
打印当前访问令牌(对脚本有用):
eve auth token
未收到邀请的用户可以请求访问:
eve auth request-access --org "My Company" --email you@example.com
eve auth request-access --org "My Company" --ssh-key ~/.ssh/id_ed25519.pub
eve auth request-access --status <request_id>
管理员通过以下方式批准或拒绝:
eve admin access-requests list
eve admin access-requests approve <request_id>
eve admin access-requests reject <request_id> --reason "..."
列表响应使用规范的 { "data": [...] } 信封。
批准是原子性的(单个数据库事务)且幂等——重新批准已完成的请求会返回现有记录。如果指纹已注册,Eve 会重用该身份所有者。如果存在与请求的 slug 和名称匹配的遗留部分组织,Eve 会在批准期间重用该组织。失败的尝试永远不会留下部分状态。
验证本地 AI 工具凭据:
eve auth creds # 显示 Claude + Codex 凭据状态
eve auth creds --claude # 仅 Claude
eve auth creds --codex # 仅 Codex
输出包括令牌类型(setup-token 或 oauth)、预览和过期时间。在同步前使用此命令确认令牌健康状况。
将本地 Claude/Codex OAuth 令牌同步到 Eve 密钥中,以便代理可以使用它们。作用域优先级:项目 > 组织 > 用户。
eve auth sync # 同步到用户级别(默认)
eve auth sync --org org_xxx # 同步到组织级别(跨组织项目共享)
eve auth sync --project proj_xxx # 同步到项目级别(限定于一个项目)
eve auth sync --dry-run # 预览而不实际同步
这会在请求的作用域设置 CLAUDE_CODE_OAUTH_TOKEN / CLAUDE_OAUTH_REFRESH_TOKEN(Claude)和 CODEX_AUTH_JSON_B64(Codex/Code)。
| 令牌前缀 | 类型 | 生命周期 | 推荐用途 |
|---|---|---|---|
sk-ant-oat01-* | setup-token(长期有效) | 长期有效 | 推荐用于作业和自动化 |
其他 sk-ant-* | oauth(短期有效) | ~15 小时 | 用于交互式开发;使用 claude setup-token 重新生成 |
eve auth sync 在同步短期 OAuth 令牌时会发出警告。在同步前运行 eve auth creds 检查令牌类型。
每次调用 harness 后,worker 会检查 Codex/Code CLI 是否在会话期间刷新了 auth.json。如果令牌发生更改,它会自动写回到原始的密钥作用域(用户/组织/项目),以便下一个作业以新令牌开始。这是透明且非致命的——回写失败会记录警告,但不会影响作业结果。
对于 Codex/Code 凭据,同步会通过比较 tokens.expires_at 从 ~/.codex/auth.json 和 ~/.code/auth.json 中选择最新的令牌。
组是一流的授权原语,用于分割数据平面访问(组织文件系统、组织文档、环境数据库)。创建组、添加成员并使用作用域约束绑定角色:
# 创建一个组
eve access groups create --org org_xxx --slug eng-team --name "Engineering"
# 添加成员
eve access groups members add eng-team --org org_xxx --user user_abc
eve access groups members add eng-team --org org_xxx --service-principal sp_xxx
# 使用作用域访问绑定角色
eve access bind --org org_xxx --group grp_xxx --role data-reader \
--scope-json '{"orgfs":{"allow_prefixes":["/shared/"]},"envdb":{"schemas":["public"]}}'
# 检查有效访问权限
eve access memberships --org org_xxx --user user_abc
| 资源 | 作用域字段 | 示例 |
|---|---|---|
| 组织文件系统 | orgfs.allow_prefixes, orgfs.read_only_prefixes | "/shared/", "/reports/" |
| 组织文档 | orgdocs.allow_prefixes, orgdocs.read_only_prefixes | "/pm/features/" |
| 环境数据库 | envdb.schemas, envdb.tables | "public", "analytics_*" |
为环境数据库中基于组的行级安全搭建 RLS 辅助函数:
eve db rls init --with-groups
这会创建 SQL 辅助函数(app.current_user_id()、app.current_group_ids()、app.has_group()),它们读取由 Eve 运行时设置的会话上下文。在 RLS 策略中使用它们:
CREATE POLICY notes_group_read ON notes FOR SELECT
USING (group_id = ANY(app.current_group_ids()));
检查主体的完整有效访问权限——基础组织/项目角色、组成员资格、已解析的绑定和合并的作用域:
eve access memberships --org org_xxx --user user_abc
eve access memberships --org org_xxx --service-principal sp_xxx
响应包括 effective_scopes(跨所有绑定合并)、effective_permissions 以及每个绑定的 matched_via(直接或通过组)。
检查并解释对特定数据平面资源的访问权限:
eve access can orgfs:read /shared/reports --org org_xxx
eve access explain orgfs:write /shared/reports --org org_xxx --user user_abc
响应包括 scope_required、scope_matched 以及每个授权的 scope_reason,解释绑定是否匹配所请求的资源路径及其原因。
在 .eve/access.yaml 中声明组、角色和作用域绑定。使用 version: 2:
version: 2
access:
groups:
eng-team:
name: Engineering Team
description: Scoped access for engineering collaborators
members:
- type: user
id: user_abc
roles:
app_editor:
scope: org
permissions:
- orgdocs:read
- orgdocs:write
- orgfs:read
- envdb:read
bindings:
- subject: { type: group, id: eng-team }
roles: [app_editor]
scope:
orgdocs: { allow_prefixes: ["/groups/app/**"] }
orgfs: { allow_prefixes: ["/groups/app/**"] }
envdb: { schemas: ["app"] }
验证、计划和同步:
eve access validate --file .eve/access.yaml
eve access plan --file .eve/access.yaml --org org_xxx
eve access sync --file .eve/access.yaml --org org_xxx
同步是声明式的:它会创建、更新和修剪组、成员、角色和绑定以匹配 YAML 文件。无效的作用域配置会在应用任何变更之前快速失败。绑定主体可以是 user、service_principal 或 group。
轮换 JWT 签名密钥:
EVE_AUTH_JWT_SECRET_NEWEVE_AUTH_KEY_ROTATION_GRACE_HOURS)后,移除旧密钥使用两个共享包为任何 Eve 部署的应用添加 Eve SSO 登录:@eve-horizon/auth(后端)和 @eve-horizon/auth-react(前端)。平台会自动将 EVE_SSO_URL、EVE_ORG_ID 和 EVE_API_URL 注入到已部署的服务中。
@eve-horizon/auth)安装:npm install @eve-horizon/auth
三个导出处理完整的后端认证表面:
| 导出 | 行为 |
|---|---|
eveUserAuth() | 非阻塞中间件。通过 JWKS 验证 RS256 令牌,检查组织成员资格,附加 req.eveUser: { id, email, orgId, role }。在令牌缺失/无效时静默通过。 |
eveAuthGuard() | 如果未设置 req.eveUser 则返回 401。放置在受保护的路由上。 |
eveAuthConfig() | 从自动注入的环境变量返回 { sso_url, eve_api_url, ... } 的处理程序。前端获取此信息以发现 SSO。 |
用于代理/服务场景的额外导出:
| 导出 | 行为 |
|---|---|
eveAuthMiddleware() | 用于代理/作业令牌的阻塞中间件。附加包含完整 EveTokenClaims 的 req.agent。失败时返回 401。 |
verifyEveToken(token) | 基于 JWKS 的本地验证(15 分钟缓存)。返回 EveTokenClaims。 |
verifyEveTokenRemote(token) | 通过 /auth/token/verify 进行 HTTP 验证。始终是最新的。 |
Express 设置(约 3 行):
import { eveUserAuth, eveAuthGuard, eveAuthConfig, eveAuthMe } from '@eve-horizon/auth';
app.use(eveUserAuth());
app.get('/auth/config', eveAuthConfig());
app.get('/auth/me', eveAuthMe()); // 供 React SDK 使用的完整响应
app.use('/api', eveAuthGuard());
NestJS 设置 —— 在 main.ts 中全局应用 eveUserAuth(),然后使用一个精简的守卫包装器:
// main.ts
import { eveUserAuth } from '@eve-horizon/auth';
app.use(eveUserAuth());
// auth.guard.ts -- 精简的 NestJS 适配器
@Injectable()
export class EveGuard implements CanActivate {
canActivate(ctx: ExecutionContext): boolean {
const req = ctx.switchToHttp().getRequest();
if (!req.eveUser) throw new UnauthorizedException();
return true;
}
}
// auth-config.controller.ts
@Controller()
export class AuthConfigController {
private handler = eveAuthConfig();
@Get('auth/config')
getConfig(@Req() req, @Res() res) { this.handler(req, res); }
}
验证策略 :eveUserAuth() 默认为 'local'(JWKS,缓存 15 分钟)。使用 strategy: 'remote' 以在每次请求约 50 毫秒的延迟下获得即时的成员资格新鲜度。
自定义角色映射 :如果你的应用需要超越 Eve 的 owner/admin/member 的角色,请在 eveUserAuth() 之后桥接:
app.use((req, _res, next) => {
if (req.eveUser) {
req.user = { ...req.eveUser, appRole: req.eveUser.role === 'member' ? 'viewer' : 'admin' };
}
next();
});
@eve-horizon/auth-react)安装:npm install @eve-horizon/auth-react
| 导出 | 用途 |
|---|---|
EveAuthProvider | 上下文提供者。引导会话:检查 sessionStorage,探测 SSO /session,缓存令牌。 |
useEveAuth() | 钩子:{ user, loading, error, config, loginWithSso, loginWithToken, logout } |
EveLoginGate | 认证时渲染子组件,否则渲染登录表单。 |
EveLoginForm | 内置的 SSO + 令牌粘贴登录 UI。 |
createEveClient(baseUrl?) | 带有自动 Bearer 注入的 Fetch 包装器。 |
简单设置 —— EveLoginGate 处理加载/登录/认证状态:
import { EveAuthProvider, EveLoginGate } from '@eve-horizon/auth-react';
<EveAuthProvider apiUrl="/api">
<EveLoginGate>
<ProtectedApp />
</EveLoginGate>
</EveAuthProvider>
自定义认证网关 —— 使用 useEveAuth() 完全控制加载、登录和错误状态:
import { EveAuthProvider, useEveAuth } from '@eve-horizon/auth-react';
function AuthGate() {
const { user, loading, loginWithToken, loginWithSso, logout } = useEveAuth();
if (loading) return <Spinner />;
if (!user) return <LoginPage onSso={loginWithSso} onToken={loginWithToken} />;
return <App user={user} onLogout={logout} />;
}
export default () => (
<EveAuthProvider apiUrl="/api">
<AuthGate />
</EveAuthProvider>
);
带认证的 API 调用 :使用 createEveClient() 实现自动 Bearer 令牌注入:
import { createEveClient } from '@eve-horizon/auth-react';
const client = createEveClient('/api');
const res = await client.fetch('/data');
该 SDK 用约 50 行代码替换了约 700-800 行手动编写的认证代码。删除自定义的 JWKS/令牌验证、Bearer 提取中间件、SSO URL 发现、会话探测逻辑、令牌存储助手和登录表单。保留应用特定的角色映射和本地密码认证。
有关完整的迁移清单、类型参考、令牌生命周期和高级模式(SSE 认证、令牌粘贴模式、令牌陈旧性),请参阅 references/app-sso-integration.md。
Eve 不代理推理流量。所有模型访问都是 BYOK(自带密钥):harnesses 和应用通过密钥自带 API 密钥,并直接调用提供商。
将 LLM 提供商密钥存储为项目密钥:
eve secrets set ANTHROPIC_API_KEY "sk-ant-xxx" --project proj_xxx
eve secrets set OPENAI_API_KEY "sk-xxx" --project proj_xxx
eve secrets set OPENAI_BASE_URL "https://my-vllm.runpod.ai/v1" --project proj_xxx
Harnesses 会自动解析这些密钥。对于自托管模型(通过 Tailscale 的 vLLM、LM Studio),将基础 URL 和 API 密钥设置为密钥——Eve 通过私有端点提供连接性(参见 eve-deploy-debugging),而不是一个托管的推理层。
每个组织都为其 Google Drive、Slack 和其他集成自带 OAuth 应用凭据。没有集群级别的共享密钥。
# 查看设置说明(重定向 URI,所需作用域)
eve integrations setup-info google-drive
eve integrations setup-info slack
# 注册 OAuth 应用凭据
eve integrations configure google-drive \
--client-id "xxx.apps.googleusercontent.com" \
--client-secret "GOCSPX-xxx" \
--label "Acme Corp Google Drive"
eve integrations configure slack \
--client-id "12345.67890" \
--client-secret "abc123" \
--signing-secret "def456" \
--app-id "A0123ABC" \
--label "Acme Corp Slack Bot"
# 查看当前配置(密钥已脱敏)
eve integrations config google-drive
# 然后像之前一样连接(使用按组织凭据)
eve integrations connect google-drive
eve integrations connect slack
优势:每个组织的凭据隔离、自定义同意屏幕品牌、独立的速率限制、无共享密钥爆炸半径。
角色和组织成员资格的更改立即生效——服务器从实时数据库成员资格解析权限,而非过时的 JWT 声明。当请求包含 project_id 但不包含 org_id 时,权限守卫会从项目所属组织派生组织上下文。
Auth SDK(@eve-horizon/auth)通过 eveUserAuth() 中间件暴露此功能。在需要时使用 strategy: 'remote' 以获得即时的成员资格新鲜度。
# 设置一个密钥
eve secrets set API_KEY "your-api-key" --project proj_xxx
# 列出密钥(不显示值)
eve secrets list --project proj_xxx
# 删除一个密钥
eve secrets delete API_KEY --project proj_xxx
# 从文件导入
eve secrets import .env --project proj_xxx
在 .eve/manifest.yaml 中使用 ${secret.KEY} 引用密钥:
services:
api:
environment:
API_KEY: ${secret.API_KEY}
在部署前验证所有必需的密钥是否已设置:
eve manifest validate --validate-secrets # 检查密钥引用
eve manifest validate --strict # 在缺少密钥时失败
对于本地开发,创建 .eve/dev-secrets.yaml(已 gitignore):
secrets:
default:
API_KEY: local-dev-key
DB_PASSWORD: local-password
staging:
DB_PASSWORD: staging-password
在作业执行时,已解析的密钥作为环境变量注入到 worker 容器中。文件类型的密钥被写入磁盘,并通过 EVE_SECRETS_FILE 引用。文件在代理进程读取后被移除。
Worker 使用密钥进行仓库访问:
github_token 密钥 → Authorization: Bearer 请求头ssh_key 密钥 → 写入 ~/.ssh/ 并通过 GIT_SSH_COMMAND 使用| 问题 | 解决方法 |
|---|---|
| 未认证 | 运行 eve auth login |
| 令牌过期 | 重新运行 eve auth login(如果距离过期 5 分钟内,令牌会自动刷新) |
| 引导已完成 | 使用 eve auth login(现有用户)或 eve admin invite(新用户)。在非生产堆栈上,eve auth bootstrap 会自动尝试服务器恢复。对于错误邮箱恢复:eve auth bootstrap --email correct@example.com |
| 密钥缺失 | 使用 eve secrets list 确认并设置密钥 |
| 插值错误 | 验证 ${secret.KEY} 拼写;运行 eve manifest validate --validate-secrets |
| Git 克隆失败 | 检查 github_token 或 ssh_key 密钥是否已设置 |
| 服务无法访问 API | 验证 EVE_API_URL 是否已注入(检查 eve env show) |
| 作用域访问被拒绝 | 运行 eve access explain <permission> <resource> --org <org> 查看作用域匹配详情。检查绑定的作用域约束是否包含目标路径/模式 |
| 显示的角色错误 | 角色从实时数据库成员资格解析。运行 eve auth permissions 查看有效角色。如果是多组织,检查 eve auth status 以获取每个组织的成员资格列表 |
| 作业中使用短期 Claude 令牌 | 运行 eve auth creds 检查令牌类型。如果是 oauth(非 setup-token),使用 claude setup-token 重新生成,然后使用 eve auth sync 重新同步 |
| 作业间 Codex 令牌过期 | 自动回写应刷新它。如果没有,重新运行 eve auth sync。检查 ~/.codex/auth.json 或 ~/.code/auth.json 是否有新令牌 |
| 应用 SSO 不工作 | 验证 EVE_SSO_URL 是否已注入(eve env show)。对于本地开发,手动设置 EVE_SSO_URL、EVE_ORG_ID 和 EVE_API_URL |
| 应用令牌中组织成员资格陈旧 | 默认 1 天 TTL。在 eveUserAuth() 中使用 strategy: 'remote' 进行即时成员资格检查 |
如果密钥可能已泄露:
eve secrets set 轮换密钥eve job list 以查找最近使用该密钥的作业每周安装数
157
仓库
首次出现
2026年2月8日
安全审计
安装于
gemini-cli157
codex157
claude-code152
pi56
opencode40
github-copilot40
| User Token | eve auth login | Interactive CLI sessions |
| Job Token | Worker auto-issued | Agent execution within jobs |
| Minted Token | eve auth mint | Bot/service accounts |
JWT payloads include sub (user ID), org_id, scope, and exp. Verify tokens via the JWKS endpoint: GET /auth/jwks.
Role and org membership changes take effect immediately -- the server resolves permissions from live DB memberships, not stale JWT claims. When a request includes a project_id but no org_id, the permission guard derives the org context from the project's owning org.
Check what the current token can do:
eve auth permissions
Register additional identities for multi-provider access:
curl -X POST "$EVE_API_URL/auth/identities" -H "Authorization: Bearer $TOKEN" \
-d '{"provider": "nostr", "external_id": "<pubkey>"}'
Eve supports pluggable identity providers. The auth guard tries Bearer JWT first, then provider-specific request auth.
| Provider | Auth Method | Use Case |
|---|---|---|
github_ssh | SSH challenge-response | Default CLI login |
nostr | NIP-98 request auth + challenge-response | Nostr-native users |
Two paths:
eve auth login --provider nostr.Invite external users via the CLI or API:
# Invite with SSH key registration (registers key so the user can log in immediately)
eve admin invite --email user@example.com --ssh-key ~/.ssh/id_ed25519.pub --org org_xxx
# Invite with GitHub identity
eve admin invite --email user@example.com --github ghuser --org org_xxx
# Invite with web-based auth (Supabase)
eve admin invite --email user@example.com --web --org org_xxx
# API: invite targeting a Nostr pubkey
curl -X POST "$EVE_API_URL/auth/invites" -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"org_id": "org_xxx", "role": "member", "provider_hint": "nostr", "identity_hint": "<pubkey>"}'
If no auth method is specified (--github, --ssh-key, or --web), the CLI warns that the user will not be able to log in. The user can self-register later via eve auth request-access --org "Org Name" --ssh-key ~/.ssh/id_ed25519.pub --wait.
When the identity authenticates, Eve auto-provisions their account and org membership.
For app-driven onboarding, use the org-scoped invite API instead of the legacy admin invite flow:
# Create an org-scoped Supabase invite with a return URL for the app
curl -X POST "$EVE_API_URL/orgs/org_xxx/invites" \
-H "Authorization: Bearer $USER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"role": "member",
"redirect_to": "https://app.example.com/invite/complete",
"app_context": { "project_id": "proj_123" }
}'
# Search existing org members for an assignee picker
curl "$EVE_API_URL/orgs/org_xxx/members/search?q=ali" \
-H "Authorization: Bearer $USER_TOKEN"
Use a user token with orgs:invite to create or list these invites and orgs:members:read for member lookup. Invite emails should land on GoTrue's /verify path, not the OAuth callback directly. If the invite is auto-applied during the SSO exchange, Eve returns invite_redirect_to so the SSO callback can land the user back in the target app even when the email provider strips nested redirect params. Current invite onboarding establishes the SSO session first, then sends the user through /set-password before redirecting to the app.
Mint tokens for bot/service users without SSH login:
# Mint token for a bot user (creates user + membership if needed)
eve auth mint --email app-bot@example.com --org org_xxx
# With custom TTL (1-90 days, default: server configured)
eve auth mint --email app-bot@example.com --org org_xxx --ttl 90
# Scope to project with admin role
eve auth mint --email app-bot@example.com --project proj_xxx --role admin
Print the current access token (useful for scripts):
eve auth token
Users without an invite can request access:
eve auth request-access --org "My Company" --email you@example.com
eve auth request-access --org "My Company" --ssh-key ~/.ssh/id_ed25519.pub
eve auth request-access --status <request_id>
Admins approve or reject via:
eve admin access-requests list
eve admin access-requests approve <request_id>
eve admin access-requests reject <request_id> --reason "..."
List responses use the canonical { "data": [...] } envelope.
Approval is atomic (single DB transaction) and idempotent -- re-approving a completed request returns the existing record. If the fingerprint is already registered, Eve reuses that identity owner. If a legacy partial org matches the requested slug and name, Eve reuses it during approval. Failed attempts never leave partial state.
Verify local AI tool credentials:
eve auth creds # Show Claude + Codex cred status
eve auth creds --claude # Only Claude
eve auth creds --codex # Only Codex
Output includes token type (setup-token or oauth), preview, and expiry. Use this to confirm token health before syncing.
Sync local Claude/Codex OAuth tokens into Eve secrets so agents can use them. Scope precedence: project > org > user.
eve auth sync # Sync to user-level (default)
eve auth sync --org org_xxx # Sync to org-level (shared across org projects)
eve auth sync --project proj_xxx # Sync to project-level (scoped to one project)
eve auth sync --dry-run # Preview without syncing
This sets CLAUDE_CODE_OAUTH_TOKEN / CLAUDE_OAUTH_REFRESH_TOKEN (Claude) and CODEX_AUTH_JSON_B64 (Codex/Code) at the requested scope.
| Token Prefix | Type | Lifetime | Recommendation |
|---|---|---|---|
sk-ant-oat01-* | setup-token (long-lived) | Long-lived | Preferred for jobs and automation |
Other sk-ant-* | oauth (short-lived) | ~15 hours | Use for interactive dev; regenerate with claude setup-token |
eve auth sync warns when syncing a short-lived OAuth token. Run eve auth creds to inspect token type before syncing.
After each harness invocation, the worker checks if the Codex/Code CLI refreshed auth.json during the session. If the token changed, it is automatically written back to the originating secret scope (user/org/project) so the next job starts with a fresh token. This is transparent and non-fatal -- a write-back failure logs a warning but does not affect the job result.
For Codex/Code credentials, the sync picks the freshest token across ~/.codex/auth.json and ~/.code/auth.json by comparing tokens.expires_at.
Groups are first-class authorization primitives that segment data-plane access (org filesystem, org docs, environment databases). Create groups, add members, and bind roles with scoped constraints:
# Create a group
eve access groups create --org org_xxx --slug eng-team --name "Engineering"
# Add members
eve access groups members add eng-team --org org_xxx --user user_abc
eve access groups members add eng-team --org org_xxx --service-principal sp_xxx
# Bind a role with scoped access
eve access bind --org org_xxx --group grp_xxx --role data-reader \
--scope-json '{"orgfs":{"allow_prefixes":["/shared/"]},"envdb":{"schemas":["public"]}}'
# Check effective access
eve access memberships --org org_xxx --user user_abc
| Resource | Scope Fields | Example |
|---|---|---|
| Org Filesystem | orgfs.allow_prefixes, orgfs.read_only_prefixes | "/shared/", "/reports/" |
| Org Documents | orgdocs.allow_prefixes, orgdocs.read_only_prefixes | "/pm/features/" |
| Environment DB | envdb.schemas, envdb.tables | "public", "analytics_*" |
Scaffold RLS helper functions for group-based row-level security in environment databases:
eve db rls init --with-groups
This creates SQL helpers (app.current_user_id(), app.current_group_ids(), app.has_group()) that read session context set by Eve's runtime. Use them in RLS policies:
CREATE POLICY notes_group_read ON notes FOR SELECT
USING (group_id = ANY(app.current_group_ids()));
Inspect a principal's full effective access -- base org/project roles, group memberships, resolved bindings, and merged scopes:
eve access memberships --org org_xxx --user user_abc
eve access memberships --org org_xxx --service-principal sp_xxx
The response includes effective_scopes (merged across all bindings), effective_permissions, and each binding's matched_via (direct or group).
Check and explain access against a specific data-plane resource:
eve access can orgfs:read /shared/reports --org org_xxx
eve access explain orgfs:write /shared/reports --org org_xxx --user user_abc
The response includes scope_required, scope_matched, and per-grant scope_reason explaining why a binding did or did not match the requested resource path.
Declare groups, roles, and scoped bindings in .eve/access.yaml. Use version: 2:
version: 2
access:
groups:
eng-team:
name: Engineering Team
description: Scoped access for engineering collaborators
members:
- type: user
id: user_abc
roles:
app_editor:
scope: org
permissions:
- orgdocs:read
- orgdocs:write
- orgfs:read
- envdb:read
bindings:
- subject: { type: group, id: eng-team }
roles: [app_editor]
scope:
orgdocs: { allow_prefixes: ["/groups/app/**"] }
orgfs: { allow_prefixes: ["/groups/app/**"] }
envdb: { schemas: ["app"] }
Validate, plan, and sync:
eve access validate --file .eve/access.yaml
eve access plan --file .eve/access.yaml --org org_xxx
eve access sync --file .eve/access.yaml --org org_xxx
Sync is declarative: it creates, updates, and prunes groups, members, roles, and bindings to match the YAML. Invalid scope configurations fail fast before any mutations are applied. Binding subjects can be user, service_principal, or group.
Rotate the JWT signing key:
EVE_AUTH_JWT_SECRET_NEW alongside the existing secretEVE_AUTH_KEY_ROTATION_GRACE_HOURS), remove the old secretAdd Eve SSO login to any Eve-deployed app using two shared packages: @eve-horizon/auth (backend) and @eve-horizon/auth-react (frontend). The platform auto-injects EVE_SSO_URL, EVE_ORG_ID, and EVE_API_URL into deployed services.
@eve-horizon/auth)Install: npm install @eve-horizon/auth
Three exports handle the full backend auth surface:
| Export | Behavior |
|---|---|
eveUserAuth() | Non-blocking middleware. Verifies RS256 token via JWKS, checks org membership, attaches req.eveUser: { id, email, orgId, role }. Passes through silently on missing/invalid tokens. |
eveAuthGuard() | Returns 401 if req.eveUser not set. Place on protected routes. |
eveAuthConfig() | Handler returning { sso_url, eve_api_url, ... } from auto-injected env vars. Frontend fetches this to discover SSO. |
Additional exports for agent/service scenarios:
| Export | Behavior |
|---|---|
eveAuthMiddleware() | Blocking middleware for agent/job tokens. Attaches req.agent with full EveTokenClaims. Returns 401 on failure. |
verifyEveToken(token) | JWKS-based local verification (15-min cache). Returns EveTokenClaims. |
verifyEveTokenRemote(token) | HTTP verification via /auth/token/verify. Always current. |
Express setup (~3 lines):
import { eveUserAuth, eveAuthGuard, eveAuthConfig, eveAuthMe } from '@eve-horizon/auth';
app.use(eveUserAuth());
app.get('/auth/config', eveAuthConfig());
app.get('/auth/me', eveAuthMe()); // Full response for React SDK
app.use('/api', eveAuthGuard());
NestJS setup -- apply eveUserAuth() globally in main.ts, then use a thin guard wrapper:
// main.ts
import { eveUserAuth } from '@eve-horizon/auth';
app.use(eveUserAuth());
// auth.guard.ts -- thin NestJS adapter
@Injectable()
export class EveGuard implements CanActivate {
canActivate(ctx: ExecutionContext): boolean {
const req = ctx.switchToHttp().getRequest();
if (!req.eveUser) throw new UnauthorizedException();
return true;
}
}
// auth-config.controller.ts
@Controller()
export class AuthConfigController {
private handler = eveAuthConfig();
@Get('auth/config')
getConfig(@Req() req, @Res() res) { this.handler(req, res); }
}
Verification strategies : eveUserAuth() defaults to 'local' (JWKS, cached 15 min). Use strategy: 'remote' for immediate membership freshness at ~50ms latency per request.
Custom role mapping : If your app needs roles beyond Eve's owner/admin/member, bridge after eveUserAuth():
app.use((req, _res, next) => {
if (req.eveUser) {
req.user = { ...req.eveUser, appRole: req.eveUser.role === 'member' ? 'viewer' : 'admin' };
}
next();
});
@eve-horizon/auth-react)Install: npm install @eve-horizon/auth-react
| Export | Purpose |
|---|---|
EveAuthProvider | Context provider. Bootstraps session: checks sessionStorage, probes SSO /session, caches tokens. |
useEveAuth() | Hook: { user, loading, error, config, loginWithSso, loginWithToken, logout } |
EveLoginGate | Renders children when authenticated, login form otherwise. |
EveLoginForm | Built-in SSO + token-paste login UI. |
createEveClient(baseUrl?) | Fetch wrapper with automatic Bearer injection. |
Simple setup -- EveLoginGate handles the loading/login/authenticated states:
import { EveAuthProvider, EveLoginGate } from '@eve-horizon/auth-react';
<EveAuthProvider apiUrl="/api">
<EveLoginGate>
<ProtectedApp />
</EveLoginGate>
</EveAuthProvider>
Custom auth gate -- use useEveAuth() for full control over loading, login, and error states:
import { EveAuthProvider, useEveAuth } from '@eve-horizon/auth-react';
function AuthGate() {
const { user, loading, loginWithToken, loginWithSso, logout } = useEveAuth();
if (loading) return <Spinner />;
if (!user) return <LoginPage onSso={loginWithSso} onToken={loginWithToken} />;
return <App user={user} onLogout={logout} />;
}
export default () => (
<EveAuthProvider apiUrl="/api">
<AuthGate />
</EveAuthProvider>
);
API calls with auth : Use createEveClient() for automatic Bearer token injection:
import { createEveClient } from '@eve-horizon/auth-react';
const client = createEveClient('/api');
const res = await client.fetch('/data');
The SDK replaces ~700-800 lines of hand-rolled auth with ~50 lines. Delete custom JWKS/token verification, Bearer extraction middleware, SSO URL discovery, session probe logic, token storage helpers, and login form. Keep app-specific role mapping and local password auth.
For the full migration checklist, types reference, token lifecycle, and advanced patterns (SSE auth, token paste mode, token staleness), see references/app-sso-integration.md.
Eve does not proxy inference traffic. All model access is BYOK (Bring Your Own Keys): harnesses and apps bring their own API keys via secrets and call providers directly.
Store LLM provider keys as project secrets:
eve secrets set ANTHROPIC_API_KEY "sk-ant-xxx" --project proj_xxx
eve secrets set OPENAI_API_KEY "sk-xxx" --project proj_xxx
eve secrets set OPENAI_BASE_URL "https://my-vllm.runpod.ai/v1" --project proj_xxx
Harnesses resolve these automatically. For self-hosted models (vLLM, LM Studio via Tailscale), set the base URL and API key as secrets -- Eve provides connectivity via private endpoints (see eve-deploy-debugging), not a managed inference layer.
Each org brings its own OAuth app credentials for Google Drive, Slack, and other integrations. No cluster-level shared secrets.
# View setup instructions (redirect URIs, required scopes)
eve integrations setup-info google-drive
eve integrations setup-info slack
# Register OAuth app credentials
eve integrations configure google-drive \
--client-id "xxx.apps.googleusercontent.com" \
--client-secret "GOCSPX-xxx" \
--label "Acme Corp Google Drive"
eve integrations configure slack \
--client-id "12345.67890" \
--client-secret "abc123" \
--signing-secret "def456" \
--app-id "A0123ABC" \
--label "Acme Corp Slack Bot"
# View current config (secrets redacted)
eve integrations config google-drive
# Then connect as before (uses per-org credentials)
eve integrations connect google-drive
eve integrations connect slack
Benefits: isolated credentials per org, custom consent screen branding, independent rate limits, no shared-secret blast radius.
Role and org membership changes take effect immediately -- the server resolves permissions from live DB memberships, not stale JWT claims. When a request includes a project_id but no org_id, the permission guard derives the org context from the project's owning org.
The Auth SDK (@eve-horizon/auth) exposes this via eveUserAuth() middleware. Use strategy: 'remote' for immediate membership freshness when needed.
# Set a secret
eve secrets set API_KEY "your-api-key" --project proj_xxx
# List keys (no values)
eve secrets list --project proj_xxx
# Delete a secret
eve secrets delete API_KEY --project proj_xxx
# Import from file
eve secrets import .env --project proj_xxx
Reference secrets in .eve/manifest.yaml using ${secret.KEY}:
services:
api:
environment:
API_KEY: ${secret.API_KEY}
Validate that all required secrets are set before deploying:
eve manifest validate --validate-secrets # check secret references
eve manifest validate --strict # fail on missing secrets
For local development, create .eve/dev-secrets.yaml (gitignored):
secrets:
default:
API_KEY: local-dev-key
DB_PASSWORD: local-password
staging:
DB_PASSWORD: staging-password
At job execution time, resolved secrets are injected as environment variables into the worker container. File-type secrets are written to disk and referenced via EVE_SECRETS_FILE. The file is removed after the agent process reads it.
The worker uses secrets for repository access:
github_token secret → Authorization: Bearer headerssh_key secret → written to ~/.ssh/ and used via GIT_SSH_COMMAND| Problem | Fix |
|---|---|
| Not authenticated | Run eve auth login |
| Token expired | Re-run eve auth login (tokens auto-refresh if within 5 min of expiry) |
| Bootstrap already completed | Use eve auth login (existing user) or eve admin invite (new users). On non-prod stacks, eve auth bootstrap auto-attempts server recovery. For wrong-email recovery: eve auth bootstrap --email correct@example.com |
| Secret missing | Confirm with eve secrets list and set the key |
| Interpolation error | Verify ${secret.KEY} spelling; run eve manifest validate --validate-secrets |
| Git clone failed | Check github_token or ssh_key secret is set |
| Service can't reach API | Verify EVE_API_URL is injected (check eve env show) |
| Scoped access denied | Run eve access explain <permission> <resource> --org <org> to see scope match details. Check that the binding's scope constraints include the target path/schema |
| Wrong role shown | Role is resolved from live DB memberships. Run eve auth permissions to see effective role. If multi-org, check eve auth status for per-org membership listing |
| Short-lived Claude token in jobs | Run eve auth creds to check token type. If oauth (not setup-token), regenerate with claude setup-token then re-sync with eve auth sync |
| Codex token expired between jobs | Automatic write-back should refresh it. If not, re-run eve auth sync. Check that ~/.codex/auth.json or ~/.code/auth.json has a fresh token |
| App SSO not working | Verify EVE_SSO_URL is injected (eve env show). For local dev, set EVE_SSO_URL, EVE_ORG_ID, and EVE_API_URL manually |
| Stale org membership in app tokens | Default 1-day TTL. Use strategy: 'remote' in eveUserAuth() for immediate membership checks |
If a secret may be compromised:
eve secrets seteve job list for recent jobs that used the secretWeekly Installs
157
Repository
First Seen
Feb 8, 2026
Security Audits
Installed on
gemini-cli157
codex157
claude-code152
pi56
opencode40
github-copilot40