Skip to main content

Authentication

2. Authentication

2.1 Authentication Methods

MethodTypical use
API KeyApplication integrations, EMR, OpenAI-compat clients
JWT BearerWebApp, admin CRUD, agents, sessions, automations
Google OAuthInteractive SPA login (DefaultLoginMode=Google in many deploys)
Delegation tokenExternal services acting for a federated user on MCP instance APIs
Local loginDev / testing only (Authentication:EnableLocalLogin)

Discover which login modes are enabled:

curl -s https://your-kroov-host/api/auth/options
{
"enableLocalLogin": false,
"enableGoogleLogin": true,
"googleOAuthConfigured": true,
"defaultLoginMode": "Google",
"mobileOAuthSupported": true
}

2.2 API Key Authentication

  1. An administrator creates a key via POST /api/auth/apikey (or project rotate-key flows)
  2. The key may be scoped to a project
  3. Clients send x-api-key: <secret> on every request
  4. On /v1/*, Authorization: Bearer <api-key> is also accepted (rewritten to ApiKey)
POST /api/ai/query HTTP/1.1
Host: your-kroov-host
x-api-key: ainx_...
Content-Type: application/json

{ "prompt": "Your query here", "projectId": 1 }

Security:

  • Store keys in a secrets manager / environment variables — never in source control
  • Rotate periodically; use separate keys for dev and production
  • Never put keys in URL query strings except for WebSocket clients that cannot set headers (prefer first-message auth when possible)
  • Never log raw keys

Create / list API keys (JWT required)

# Create — secret returned once
curl -X POST https://your-kroov-host/api/auth/apikey \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{ "name": "EMR Integration", "projectId": 1, "expiresAt": null }'

# List summaries (no secrets)
curl "https://your-kroov-host/api/auth/apikey?projectId=1" \
-H "Authorization: Bearer $JWT"
{
"key": "ainx_...",
"name": "EMR Integration",
"expiresAt": null
}

2.3 JWT Authentication

Obtain via local login (dev):

curl -X POST https://your-kroov-host/api/auth/login \
-H "Content-Type: application/json" \
-d '{ "username": "your-username", "password": "your-password" }'

Google OAuth: GET /api/auth/google (browser redirect).

Refresh / logout:

curl -X POST .../api/auth/refresh -H 'Content-Type: application/json' \
-d '{ "refreshToken": "..." }'
curl -X POST .../api/auth/logout -H 'Content-Type: application/json' \
-d '{ "refreshToken": "..." }'

Using JWT:

GET /api/project HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Typical lifetimes: access token ~60 minutes; refresh token ~7 days (deployment-configurable).

Admin policy (AdminOnly): JWT Admin role, or Google admin email allow-list. Many write endpoints require it.

2.4 Google OAuth

  • GET /api/auth/google — start Google OAuth (browser redirect). The SPA continues to receive tokens in the URL fragment (#token=…&refreshToken=…) after Google returns to /api/auth/google/callback.

SPA token hand-off (useful in headless / local testing): navigate to
/login?token=<JWT>&refreshToken=<REFRESH> after calling the login API.

Native / mobile Google login (PKCE)

When GET /api/auth/options reports mobileOAuthSupported: true, a native app can complete Google sign-in without putting JWTs in a URL. Google still uses the existing Web OAuth client and the HTTPS callback registered in Google Cloud Console ({PublicOrigin}/api/auth/google/callback). The app never talks to Google directly.

  1. Open the system browser (not a WebView) on:

    GET /api/auth/google?client=mobile&code_challenge=<S256>&code_challenge_method=S256&redirect_uri=kroov%3A%2F%2Fauth-callback

    • code_challenge / code_challenge_method — PKCE S256 (RFC 7636)
    • redirect_uri — must match the server allowlist (Authentication:Google:MobileRedirectUris, default kroov://auth-callback). This URI is not added to Google Cloud Console.
  2. After Google sign-in, the API redirects the browser to kroov://auth-callback?code=… (errors: ?error=…). The code is a ~60 second, one-shot ticket; it is not a JWT.

  3. Exchange once:

curl -s -X POST https://your-kroov-host/api/auth/google/exchange \
-H 'Content-Type: application/json' \
-d '{ "code": "<from-callback>", "codeVerifier": "<pkce-verifier>" }'

The JSON is the same as local login: { "token", "refreshToken", "username" }. Replay, a wrong verifier, or an expired ticket all return 400 with the same message.

Afterwards use Authorization: Bearer, POST /api/auth/refresh, and POST /api/auth/logout as for any other session. Google is not called again until logout, refresh expiry (~7 days of inactivity, sliding window), or revocation. Signing in on another device no longer revokes existing Google sessions; use DELETE /api/me/sessions/{id} to drop a specific device.

2.5 Delegation tokens (federation)

External apps exchange verified service + user identity for a short-lived X-AINexus-Delegation token used with MCP instance APIs and chat:

curl -X POST https://your-kroov-host/api/delegations/exchange \
-H "x-api-key: PROJECT_API_KEY" \
-H "X-AINexus-Service-Token: <gcp-sa-jwt>" \
-H "X-AINexus-User-Token: <google-user-jwt>" \
-H "Content-Type: application/json" \
-d '{ "phoneProof": "<optional-eva-phone-jwt>", "scopes": ["mcp:pair", "mcp:invoke", "mcp:manage"] }'
{ "token": "...", "tokenType": "Kroov-Delegation", "expiresIn": 300 }

See Projects → Federation and MCP.

2.6 Global authorization notes

  • The API applies a fallback RequireAuthenticatedUser policy: controllers must explicitly [AllowAnonymous] for public routes (auth options, features, webhooks, health, CSP report, MCP proxy with controller token, etc.).
  • JSON uses camelCase everywhere.
  • Multipart field names match property names (providerId, projectId, noLog, …).