Skip to main content

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 login to get a new token.
  • Tokens have a TTL (default 24 hours). When it expires, you re-authenticate — there's no silent refresh.
tip

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:

StateWhat happenedWhat you can do
InvitedAdmin created an invite for your emailNothing yet — register your key
Key registeredYour SSH public key is stored in EveAuthenticate with eve auth login
AuthenticatedYou hold a valid JWT tokenAccess the API, but no org resources yet
Org memberYou belong to at least one organizationAccess projects within that org
WorkingFull access to assigned projectsDeploy, 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-responseWeb SSO
Used byCLI, agents, scriptsBrowser UI
Identity proofSSH key signatureOAuth provider (GitHub, Google, etc.)
Token algorithmRS256 JWTHS256 JWT (Supabase-issued)
Where stored~/.eve/credentials.jsonBrowser cookie
Best forDeveloper workflows, CI/CDCasual browsing, dashboards
note

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
FlagDefaultDescription
--emailProfile defaultYour registered email
--ssh-key~/.ssh/id_ed25519Path to SSH private key
--ttlServer-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

RoleCapabilities
ownerFull control — can delete the org
adminManage members, projects, and settings
memberAccess 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:

  1. Register your SSH key (or let auto-discovery handle it).
  2. Run eve auth login --email your-email@example.com.
  3. 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 typeIssued toPurposeLifetime
UserHuman usersCLI access, interactive work1–90 days (configurable)
JobRunning jobsScoped API access during executionJob duration
MintedService accounts, botsProgrammatic access without SSH keys1–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:

ModeTriggerToken requiredUse case
auto-openFresh deploy, no usersNoInitial setup (10-minute window)
recoveryTrigger file on hostNoLost admin access
secureEVE_BOOTSTRAP_TOKEN setYesProduction 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
warning

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.

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.

PackageRuntimeWhat it does
@eve-horizon/authNode.js (Express / NestJS)Verify JWTs, check org membership, serve auth config
@eve-horizon/auth-reactReactSession 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.

tip

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:

  1. Checks sessionStorage for a cached token and validates it.
  2. If no cached token, fetches /auth/config from your backend to discover the SSO URL.
  3. Probes the SSO broker's /session endpoint (using the root-domain cookie) to get a fresh token.
  4. 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:

StrategyHow it worksLatencyBest for
local (default)Fetches Eve's JWKS keys and caches them for 15 minutesFastUser-facing apps
remoteCalls Eve's /auth/token/verify on every request~50ms per requestWhen 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:

VariableDescription
EVE_API_URLInternal API URL for server-to-server calls
EVE_PUBLIC_API_URLPublic-facing API URL
EVE_SSO_URLSSO broker URL
EVE_ORG_IDOrganization ID
EVE_PROJECT_IDProject ID
EVE_ENV_NAMEEnvironment 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}"
warning

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:

TokenStorageTTLWhat happens when it expires
Eve RS256 access tokensessionStorage1 day (default)SDK re-probes the SSO session
SSO refresh cookiehttpOnly cookie (root domain)30 daysGoTrue refreshes it
GoTrue refresh tokenhttpOnly cookie (SSO broker)30 daysUser 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

ProblemFix
Not authenticatedeve auth login
Token expiredRe-run eve auth login
Bootstrap already completedUse eve auth login (existing user) or eve admin invite (new users)
No matching key for tokenToken was signed with a rotated key — re-authenticate
Challenge expiredChallenges are valid for 5 minutes — request a new one

What's next?