Authentication & Identity
Before you deploy, create jobs, or manage secrets, Eve needs to know who you are. This guide walks through how identity works in Eve — from your first login to managing teams across multiple organizations.
What does it mean to be authenticated?
Eve uses a stateless authentication model. There are no server-side sessions. When you log in, Eve issues a signed JWT token that proves your identity. The token is stored locally on your machine and attached to every CLI command you run.
What this means in practice:
- No central session to expire on the server — your token is self-contained. Eve verifies the signature, not a session store.
- Your token lives in
~/.eve/credentials.json— scoped by API URL so you can authenticate against multiple Eve instances. - If you delete the file, you're logged out. Re-run
eve auth loginto get a new token. - Tokens have a TTL (default 24 hours). When it expires, you re-authenticate — there's no silent refresh.
Run eve auth whoami at any time to see your current identity, org memberships, and token expiry.
The user journey
A new user goes through a clear progression from invitation to productive work:
Each state is distinct:
| State | What happened | What you can do |
|---|---|---|
| Invited | Admin created an invite for your email | Nothing yet — register your key |
| Key registered | Your SSH public key is stored in Eve | Authenticate with eve auth login |
| Authenticated | You hold a valid JWT token | Access the API, but no org resources yet |
| Org member | You belong to at least one organization | Access projects within that org |
| Working | Full access to assigned projects | Deploy, create jobs, manage secrets |
How login works: SSH vs Web SSO
Eve supports two ways to prove your identity. SSH challenge-response is the default for CLI users. Web SSO (via Supabase Auth) is available for browser-based login.
SSH challenge-response
The CLI handles this automatically — you don't need to understand the protocol to use it. But if you're curious:
The key insight: Eve never sees your private key. It only verifies that you can sign a challenge with the key whose public half is registered to your account.
Comparison
| SSH challenge-response | Web SSO | |
|---|---|---|
| Used by | CLI, agents, scripts | Browser UI |
| Identity proof | SSH key signature | OAuth provider (GitHub, Google, etc.) |
| Token algorithm | RS256 JWT | HS256 JWT (Supabase-issued) |
| Where stored | ~/.eve/credentials.json | Browser cookie |
| Best for | Developer workflows, CI/CD | Casual browsing, dashboards |
GitHub is NOT required for SSH login. Eve uses your SSH key directly. GitHub is only used as a convenience for discovering and importing your public key — you can register any SSH key manually.
Your first login
eve auth login --email user@example.com
That's it. The CLI handles the challenge-response flow, stores the token, and you're ready to work.
Options
eve auth login --email user@example.com --ssh-key ~/.ssh/id_rsa --ttl 7
| Flag | Default | Description |
|---|---|---|
--email | Profile default | Your registered email |
--ssh-key | ~/.ssh/id_ed25519 | Path to SSH private key |
--ttl | Server-configured (1 day) | Token TTL in days (1–90) |
Profile defaults
Set defaults so future logins are a single command:
eve config set --default-email user@example.com
eve config set --default-ssh-key ~/.ssh/id_ed25519
# With defaults set, login becomes:
eve auth login
GitHub key auto-discovery
When login fails because no SSH key is registered, the CLI offers to fetch your keys from GitHub:
$ eve auth login --email user@example.com
Error: No SSH key registered for this email
Would you like to fetch your SSH keys from GitHub? [y/N]: y
GitHub username: myuser
Found 2 SSH keys for myuser:
1. ssh-ed25519 AAAA... (added 2024-01-15)
2. ssh-rsa AAAA... (added 2023-06-20)
Select a key to register [1]: 1
Key registered successfully. Retrying login...
This is a convenience feature. You can always register a key manually if you don't use GitHub.
Organizations and roles
Eve's access model is built around organizations. Every project belongs to an org. Every user belongs to one or more orgs with a specific role.
You can belong to multiple organizations simultaneously, with different roles in each. You can also create your own organizations.
Org roles
| Role | Capabilities |
|---|---|
owner | Full control — can delete the org |
admin | Manage members, projects, and settings |
member | Access projects and create jobs |
Managing membership
# List org members
eve org members --org org_xxx
# Add a member
eve org members add user@example.com --role admin --org org_xxx
# Remove a member
eve org members remove user_abc --org org_xxx
Project-level membership follows the same pattern:
eve project members --project proj_xxx
eve project members add user@example.com --role admin --project proj_xxx
eve project members remove user_abc --project proj_xxx
Checking your permissions
# See your identity, orgs, and roles
eve auth whoami
# View the full permission catalog
eve auth permissions
Getting invited and requesting access
Receiving an invite
If someone invites you, they'll share an email or link. On your side:
- Register your SSH key (or let auto-discovery handle it).
- Run
eve auth login --email your-email@example.com. - You're in — the invite automatically grants org membership.
Inviting users (admin)
# Invite with GitHub key auto-fetch
eve admin invite --email newuser@example.com --github newuser
# Invite with a specific role
eve admin invite --email newuser@example.com --github newuser --role admin
# Send a web-auth invite email (Supabase)
eve admin invite --email newuser@example.com --web
eve admin invite --email newuser@example.com --web --redirect-to https://app.example.com
Identity-targeted invites
For Nostr users or other identity providers:
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>"
}'
When an unregistered pubkey authenticates and matches an invite's identity_hint, Eve auto-provisions a user account and org membership.
Self-service access requests
Users without an invite can request access directly:
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 --org "My Company" --nostr-pubkey <hex>
# Check request status
eve auth request-access --status <request_id>
Admins review requests:
eve admin access-requests list
eve admin access-requests approve <request_id>
eve admin access-requests reject <request_id> --reason "..."
Approval is atomic — a single DB transaction. Failed attempts don't leave partial state. Re-approving a completed request is idempotent.
Token types
Eve issues three kinds of tokens for different purposes:
| Token type | Issued to | Purpose | Lifetime |
|---|---|---|---|
| User | Human users | CLI access, interactive work | 1–90 days (configurable) |
| Job | Running jobs | Scoped API access during execution | Job duration |
| Minted | Service accounts, bots | Programmatic access without SSH keys | 1–90 days |
Minting tokens for service accounts
# Mint a token (creates user + membership if needed)
eve auth mint --email app-bot@example.com --org org_xxx
# With custom TTL and role
eve auth mint --email app-bot@example.com --project proj_xxx --role admin --ttl 90
Bootstrap: creating the first admin
On a fresh Eve deployment with no users, someone needs to become the first admin. Eve supports three bootstrap modes:
| Mode | Trigger | Token required | Use case |
|---|---|---|---|
| auto-open | Fresh deploy, no users | No | Initial setup (10-minute window) |
| recovery | Trigger file on host | No | Lost admin access |
| secure | EVE_BOOTSTRAP_TOKEN set | Yes | Production lockdown |
Auto-open mode (default)
On a fresh deployment with no users, the bootstrap endpoint is open for 10 minutes:
# Check bootstrap status
eve auth bootstrap --status
# Bootstrap with your real email
eve auth bootstrap --email your-email@example.com
Use your actual email address during bootstrap. The bootstrap email becomes the admin account you'll log in with. Using a placeholder like admin@example.com will lock you out since you won't have the matching SSH key.
The window closes after 10 minutes or after the first admin is created.
Recovery mode
If you lose admin access, create a trigger file on the host:
# On the server or pod
touch /tmp/eve-bootstrap-enable
# Then from your machine (within 10 minutes)
eve auth bootstrap --email admin@example.com
The trigger file is automatically deleted after successful bootstrap.
Secure mode (recommended for production)
Set EVE_BOOTSTRAP_TOKEN to require a token for all bootstrap attempts:
# Server environment
EVE_BOOTSTRAP_TOKEN=your-secure-random-token
# Bootstrap requires the token
eve auth bootstrap --email admin@example.com --token your-secure-random-token
When NODE_ENV=production, bootstrap requires the token. If it is not set, the bootstrap window is closed entirely.
Syncing OAuth tokens
Sync local OAuth tokens (Claude, Codex) into Eve secrets so agent harnesses can use them:
eve auth sync # Sync to user-level
eve auth sync --org org_xxx # Sync to org-level
eve auth sync --project proj_xxx # Sync to project-level
eve auth sync --dry-run # Preview without syncing
Check local credential availability:
eve auth creds # Show Claude + Codex credential status
eve auth creds --claude # Only Claude
eve auth creds --codex # Only Codex
Nostr authentication
Eve supports Nostr identity via Schnorr challenge-response and NIP-98 request authentication. This allows Nostr-native users to authenticate without SSH keys.
The flow mirrors SSH auth: Eve issues a challenge, you sign it with your Nostr private key, and Eve verifies the signature against your registered public key.
To create invites targeting Nostr identities, see Identity-targeted invites above.
Adding Eve auth to your app
Everything above covers authenticating yourself with the Eve platform. This section covers the other direction: adding Eve SSO login to an app you deploy on Eve.
Eve provides two SDK packages that handle token verification, session management, and login UI so you don't have to build it from scratch.
| Package | Runtime | What it does |
|---|---|---|
@eve-horizon/auth | Node.js (Express / NestJS) | Verify JWTs, check org membership, serve auth config |
@eve-horizon/auth-react | React | Session bootstrap, login gate, auth hooks |
Backend setup
Install the backend package:
npm install @eve-horizon/auth
Then wire up three pieces of middleware:
import { eveUserAuth, eveAuthGuard, eveAuthConfig } from '@eve-horizon/auth';
// 1. Parse and verify tokens on every request (non-blocking)
app.use(eveUserAuth());
// 2. Serve auth discovery config for the frontend
app.get('/auth/config', eveAuthConfig());
// 3. Protect routes that require a logged-in user
app.get('/auth/me', eveAuthGuard(), (req, res) => {
res.json(req.eveUser);
});
// Protect an entire route tree
app.use('/api', eveAuthGuard());
eveUserAuth() is non-blocking by design. It extracts the Bearer token, verifies it against Eve's JWKS endpoint, checks org membership, and attaches req.eveUser if everything checks out. If the token is missing or invalid, the request passes through with req.eveUser unset -- this lets you serve both public and protected routes from the same app. Use eveAuthGuard() on routes where authentication is required.
For NestJS apps, use the same Express middleware in main.ts and wrap eveAuthGuard() in a thin NestJS guard:
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
@Injectable()
export class EveGuard implements CanActivate {
canActivate(ctx: ExecutionContext): boolean {
return !!ctx.switchToHttp().getRequest().eveUser;
}
}
Frontend setup
Install the React package:
npm install @eve-horizon/auth-react
Wrap your app in the auth provider and login gate:
import { EveAuthProvider, EveLoginGate } from '@eve-horizon/auth-react';
function App() {
return (
<EveAuthProvider apiUrl="/api">
<EveLoginGate>
<ProtectedApp />
</EveLoginGate>
</EveAuthProvider>
);
}
EveAuthProvider handles the full session bootstrap sequence on mount:
- Checks
sessionStoragefor a cached token and validates it. - If no cached token, fetches
/auth/configfrom your backend to discover the SSO URL. - Probes the SSO broker's
/sessionendpoint (using the root-domain cookie) to get a fresh token. - If no SSO session exists, renders the login form.
EveLoginGate renders your app when the user is authenticated, and shows a built-in login form otherwise. You can customize both the login and loading states:
<EveLoginGate
fallback={<CustomLoginPage />}
loadingFallback={<Spinner />}
>
<ProtectedApp />
</EveLoginGate>
Using auth state in components
The useEveAuth hook gives you the current user and login/logout actions:
import { useEveAuth, createEveClient } from '@eve-horizon/auth-react';
function Dashboard() {
const { user, logout } = useEveAuth();
return (
<div>
<p>Logged in as {user.email} ({user.role})</p>
<button onClick={logout}>Log out</button>
</div>
);
}
// For API calls, createEveClient auto-injects the Bearer token
const client = createEveClient('/api');
const res = await client.fetch('/data');
The user object has four fields: id, email, orgId, and role.
Token verification strategies
The backend middleware supports two verification strategies:
| Strategy | How it works | Latency | Best for |
|---|---|---|---|
local (default) | Fetches Eve's JWKS keys and caches them for 15 minutes | Fast | User-facing apps |
remote | Calls Eve's /auth/token/verify on every request | ~50ms per request | When you need immediate revocation |
// Use remote verification for sensitive endpoints
app.use(eveUserAuth({ strategy: 'remote' }));
The local strategy means membership changes (like removing a user from an org) can take up to 15 minutes to take effect. With the default 1-day token TTL, this is usually fine. Use remote when you need stronger guarantees.
Agent and job token verification
If your app receives requests from Eve agent jobs rather than human users, use eveAuthMiddleware instead:
import { eveAuthMiddleware } from '@eve-horizon/auth';
// Blocking: returns 401 immediately on any auth failure
app.use('/agent-api', eveAuthMiddleware());
app.post('/agent-api/callback', (req, res) => {
// req.agent contains the full EveTokenClaims
console.log(req.agent.job_id, req.agent.permissions);
});
Unlike eveUserAuth, this middleware is blocking -- it returns 401 on any verification failure. It defaults to remote strategy since agent tokens often need immediate validity checks.
Environment variables
When you deploy an app to Eve, the platform automatically injects these environment variables:
| Variable | Description |
|---|---|
EVE_API_URL | Internal API URL for server-to-server calls |
EVE_PUBLIC_API_URL | Public-facing API URL |
EVE_SSO_URL | SSO broker URL |
EVE_ORG_ID | Organization ID |
EVE_PROJECT_ID | Project ID |
EVE_ENV_NAME | Environment name |
The @eve-horizon/auth middleware reads EVE_API_URL, EVE_ORG_ID, and EVE_SSO_URL automatically. You don't need to pass them as options unless you want to override the defaults (for example, in local development).
You can also reference these in your manifest using interpolation syntax:
services:
web:
environment:
NEXT_PUBLIC_SSO_URL: "${SSO_URL}"
For local development outside Eve, set EVE_API_URL, EVE_ORG_ID, and EVE_SSO_URL as environment variables manually. Without them, the middleware won't know where to verify tokens or which org to check membership against.
Token lifecycle
The frontend SDK manages three tokens transparently:
| Token | Storage | TTL | What happens when it expires |
|---|---|---|---|
| Eve RS256 access token | sessionStorage | 1 day (default) | SDK re-probes the SSO session |
| SSO refresh cookie | httpOnly cookie (root domain) | 30 days | GoTrue refreshes it |
| GoTrue refresh token | httpOnly cookie (SSO broker) | 30 days | User sees the login form |
You don't need to write any token refresh logic. When the access token expires, EveAuthProvider automatically re-probes the SSO broker. If the SSO session is also expired, the user is shown the login form.
SSE authentication
The middleware supports token authentication via query parameter for Server-Sent Events connections where you can't set custom headers:
GET /api/events?token=eyJ...
Token paste mode
For local development or headless environments where SSO redirect isn't available, users can paste a token directly:
# Get a token from the CLI
eve auth token
# Paste it into the login form's "Token" tab
The built-in EveLoginForm component includes both SSO and token paste modes.
Troubleshooting
| Problem | Fix |
|---|---|
| Not authenticated | eve auth login |
| Token expired | Re-run eve auth login |
| Bootstrap already completed | Use eve auth login (existing user) or eve admin invite (new users) |
| No matching key for token | Token was signed with a rotated key — re-authenticate |
| Challenge expired | Challenges are valid for 5 minutes — request a new one |
What's next?
- Secrets & Credentials — manage multi-scope secrets for deployments and agent harnesses.
- Security & Key Rotation — JWT key rotation, incident response, and production hardening.
- CLI Reference — full command reference for
eve authandeve admin.