Skip to main content

Architecture & Token Flow

How authentication and data flow between Hrida AI Studio, hrida-mcpo, and HridaOne — so you can reason about security, debug issues, and safely extend the integration.


Component map​

Browser
│ Keycloak OIDC login (SSO) or native login
▼
Hrida AI Studio (FastAPI :8080)
│ Chat: Digital Employee filter stores the user's KC token → Redis hrida:kc_token:{user_id}
│ Workflows: run is enqueued (with the KC token) → Redis Stream hrida:workflow_jobs
▼
Workflow Worker (hrida_workflow_worker.py)
│ Writes the run's KC token → Redis hrida:kc_token:{run_id}
▼
hrida-mcpo (MCP-to-OpenAPI bridge)
│ HR tool (scripts/hr-agent/tool.py) reads hrida:kc_token:{context_id} on each call
│ Falls back to the service account when no per-user token is found
▼
HridaOne (Spring Boot :8081)
│ KcJwtAuthenticationFilter validates the KC token
│ Resolves tenant, role, and employee from token claims
│ JIT-provisions the employee on first request
▼
Result: HridaOne → hrida-mcpo → Hrida AI Studio → browser

Two identities: service account and per-user token​

Service account. hrida-mcpo signs in to HridaOne with a dedicated account (HRIDAONE_EMAIL + HRIDAONE_PASSWORD, or a pre-issued HRIDAONE_JWT_TOKEN). All HR calls run as that account; the agent narrows results to the asking user by email lookup. See HridaOne Service Account.

Per-user Keycloak token. With SSO and HRIDAONE_USE_PER_USER_TOKEN=true, each call carries the employee's own Keycloak token. This means:

  • Two employees asking the same question get their own data (leave balance, payslips)
  • HridaOne's audit log records the actual employee, not a service account
  • HridaOne's RBAC enforces scope at the API level — a crafted prompt cannot reach another employee's data

The order the HR tool tries is: per-user token from Redis → HRIDAONE_JWT_TOKEN → email/password login. See Token resolution priority.


Token lifecycle​

In chat​

  1. The user signs in to Hrida AI Studio through Keycloak; Studio keeps the OAuth session.
  2. When the user messages the HR Agent, its Digital Employee filter takes the user's newest Keycloak access token from their OAuth sessions and writes it to hrida:kc_token:{user_id} (TTL 600 s).
  3. The filter injects _context_id={user_id} into the system message; the model passes it on every tool call.
  4. The HR tool reads hrida:kc_token:{context_id} and sends it to HridaOne as Authorization: Bearer.

In workflow runs​

  1. The user starts a run (POST /api/v1/agent-workflows/{id}/run). The token from the request's Authorization header is saved on the run record (triggered_by_kc_token).
  2. The run is enqueued to the Redis Stream.
  3. The worker writes the token to hrida:kc_token:{run_id} with a TTL of WORKFLOW_MAX_TIMEOUT_SECONDS + 60 s (660 s by default), so it outlives the run.
  4. The agent passes the run ID to HR tools as _context_id.
  5. If the client disconnects mid-stream, the key is deleted immediately; otherwise it expires with its TTL.

Runs started by cron or webhook triggers have no user session, so no token — the service account is used.

Short-lived tokens

Keycloak access tokens expire quickly (5 minutes by default). If a long run outlives the token, later HridaOne calls fail with 401. Raise the realm's Access Token Lifespan (Realm Settings → Tokens) if your HR workflows run longer.

Why Redis?​

The MCP server runs as a separate process under hrida-mcpo, so environment variables set in Studio cannot reach it. Redis is the shared channel: Studio and the worker write the token, the tool reads it. Both must use the same Redis (REDIS_URL).


HridaOne auth filter​

KcJwtAuthenticationFilter runs before HridaOne's native JWT filter. Both token types work on every endpoint:

Token typeHow identifiedWhat happens
Native JWT (HS256)HS256 algorithm headerStandard HridaOne login session
Keycloak JWT (RS256)RS256 algorithm headerSignature checked against Keycloak's JWKS; iss must match KEYCLOAK_ISSUER_URI or KEYCLOAK_PUBLIC_ISSUER_URI; azp must be in KEYCLOAK_ALLOWED_CLIENT_IDS

Claims read from a Keycloak token:

ClaimUsed for
emailFinding the HridaOne user
subKeycloak user ID
name (falls back to preferred_username)Display name for JIT provisioning
hrida_tenant_codeTenant (company) the user belongs to
hrida_roleRole, when set
hrida_employee_idEmployee code, when set
JIT provisioning

When a Keycloak user reaches HridaOne for the first time, the filter creates an EMPLOYEE record under the tenant from hrida_tenant_code. Managers and admins must still be promoted in HridaOne.


Where data lives​

  • The Keycloak token lives in Redis with a short TTL and, for workflow runs, on the run record so a paused run can resume.
  • The KC token is never logged by the workflow runtime.
  • HridaOne responses (payslip amounts, leave counts) flow back into the chat or the run's output and follow Studio's normal data retention.

Network topology (Docker)​

All services share the hridaai-network Docker bridge. Only the reverse proxy is exposed to the internet.

internet
│ HTTPS :443
▼
Nginx (reverse proxy + TLS termination)
├── / → hrida-ai-studio :8080
└── HridaOne host → hridaone :8081 (only if employees use HridaOne's own UI)

internal network (hridaai-network)
hrida-ai-studio :8080
hridaone :8081
keycloak :9090
redis :6379
hrida-postgres :5432
hrida-mcpo :8000 (container)

Security properties​

PropertyHow it is achieved
Per-employee data isolationKC token carries the employee's identity; HridaOne enforces RBAC per user
Token not persisted long-termRedis TTL; key deleted early if the client disconnects
Token signature verificationRS256 checked against Keycloak's JWKS endpoint
Accepted clients restrictedazp must be listed in KEYCLOAK_ALLOWED_CLIENT_IDS
Audit trailHridaOne records the real employee as the actor
Prompt isolationHridaOne only sees tool parameters, never the raw prompt
Hrida.ai is proprietary software of Zlabs Innovation. See the license for terms. © 2026 Zlabs Innovation.