Authentication Flows
Token types, validation flows, and role system for the Helvia Platform platform. Last updated: 2026-03-18. Per-package auth details: each package has
docs/auth.md.
Token Types
| Token | Issued By | Payload | Signing | Expiry | Validated By |
|---|---|---|---|---|---|
| User JWT | hbf-core POST /login (also /login/google, /login/microsoft, /login/teams, /login/integration) | sub (user ID), email, name, type ("login"), organizationId, authorities, iat, exp | HS256, secret: app.security.token.secret (Base64-encoded) | 24h (86400s) | hbf-core TokenAuthenticationFilter; NestJS HBFGuard (calls GET /users/me) |
| Refresh Token | hbf-core (issued alongside User JWT) | Same as User JWT, type = "refresh_token" | Same as User JWT | 7 days (604800s) | hbf-core POST /login/refresh |
| Service Token (CORE_TOKEN) | Pre-provisioned per service (env var CORE_TOKEN / HBF_CORE_API_TOKEN) | type = "api_token" | Same as User JWT (HS256, same secret) | Long-lived | hbf-core TokenAuthenticationFilter |
| Pipeline JWT | helvia-rag-pipelines POST /admin/token | role ("admin" / "client"), pipeline_id (optional), iat | HS256, secret: JWT_SECRET (separate from hbf-core) | No expiry by default | helvia-rag-pipelines (local validation, no call to hbf-core) |
| DM JWT | Caller shares DM_AUTH_JWT_SECRET (no issuance endpoint in hbf-data-manager) | Any valid payload | HS256, secret: DM_AUTH_JWT_SECRET (separate from all other secrets) | Not enforced | hbf-data-manager DMJwtGuard (local validation, no call to hbf-core) |
| API Token | hbf-core POST /api-tokens (moderator-only) | sub (token ID), name, type ("api_token"), role, organizationId, isModerator | HS256, secret: APP_SECURITY_API_DEPLOYMENT_TOKEN_SECRET (separate from login secret) | Long-lived (no expiry) | hbf-core TokenAuthenticationFilter |
| Direct Line Token | External token URL (typically Lambda/API Gateway) | Opaque (Bot Framework) | Managed by Bot Framework | ~15 minutes (auto-renewed) | Azure Bot Framework Direct Line (current); open-bot-framework (planned, not yet in use) |
Flow 1: Console User Login
Sequence
Login Methods
- Credentials: Email + password. Password verified with BCrypt. Account locks after 10 failed attempts.
- Google:
POST /login/google. Verifies Google ID token server-side. - Microsoft:
POST /login/microsoft. Validates via Microsoft Graph API. - Teams:
POST /login/teams. Teams-specific SSO flow. - OIDC Integration:
POST /login/integration. Generic OIDC provider flow.
All login methods return the same { token, refreshToken, user } response shape.
Refresh Flow
Logout
On 401 response with an expired refresh token, the console clears all stored tokens (hbf/token, hbf/rfr, hbf/user) from localStorage and redirects to the login page. There is no server-side session invalidation (stateless JWT model).
Flow 2: Service-to-Service (CORE_TOKEN)
HBFGuard Pattern
NestJS services use HBFGuard to validate incoming requests from the console or other services:
- Extract
Authorization: Bearer <token>from the request header. - Call hbf-core
GET /users/mewith the token. - hbf-core validates the token via
TokenAuthenticationFilter(checks signature, issuerhbf-auth, expiry). - hbf-core returns user data including roles and organization membership.
- HBFGuard injects the user object into
request.user. - Downstream role guards (
AdminOrgRoleGuard,MemberOrgRoleGuard,CanEditTenantGuard, etc.) enforce authorization.
Some services also use JWTGuard for local JWT verification. This validates the token signature locally and checks that the sub claim matches the JWT_SUB env var, avoiding the round-trip to hbf-core.
hbf-data-manager uses a dual-guard pattern on the same service:
HBFGuardon read endpoints (GET /orgs/:orgId/**): delegates to hbf-coreGET /users/meand enforces org-level role membership.DMJwtGuardon delete endpoints (DELETE /chat-sessions/**,POST /chat-sessions/interactions/bulk-delete): validates the token locally againstDM_AUTH_JWT_SECRET(HS256). No call to hbf-core. Intended exclusively for internal daemon callers (e.g. hbf-data-retention).
Flow 3: Webchat End-User Session
User Identity Resolution
The webchat widget determines user identity in this priority order:
window.HBF_retrieveUserId()function (if defined by host page).- URL query parameter
?hsh=. - Previously stored value in localStorage.
- Generated UUID in format
hbf-{uuid}.
Flow 4: RAG Pipeline JWT
Role Scoping
- admin: Full access to all pipeline endpoints and all pipeline IDs.
- client: Scoped access. The
pipeline_idin the token must match thepipeline_idpath parameter in the request. Access to a different pipeline is denied.
Pipeline JWTs are validated entirely within helvia-rag-pipelines. There is no communication with hbf-core for pipeline auth.
Flow 5: API Token (Programmatic Access)
API tokens use a separate signing secret (APP_SECURITY_API_DEPLOYMENT_TOKEN_SECRET) from login tokens. They are long-lived with no expiry and are intended for external integrations and programmatic access. Only moderators can create them.
Flow 6: open-bot-framework — DirectLine Gateway Auth (planned, not yet in use)
Status: Planned. open-bot-framework is not currently deployed. hbf-webchat connects to hbf-bot via Azure DirectLine (Microsoft's botframework-directline, bundled in botframework-webchat). OBF is the planned self-hosted DirectLine replacement. The auth flows below document the target architecture.
open-bot-framework is a self-contained DirectLine 3.0 gateway with its own token types. It does not delegate to hbf-core for any token validation.
6a: Bot registers and obtains an access token (client credentials)
6b: Webchat client obtains a DirectLine token
6c: Bot replies to a conversation
Token refresh
Refresh accepts expired tokens (via ignoreExpiration: true), so clients can recover from expiry without losing conversation context.
Flow 7: End-User OIDC Authentication (hbf-bot)
hbf-bot authenticates end-users (chat subscribers) via OIDC using two strategies. Auth state is stored in a dedicated auth store on the session blackboard, not in regular session keys.
7a: Interactive OIDC (OidcAuthStrategy)
Config: TenantSettings.security.oidc (integration reference, requireEmailVerification, additionalClaims).
7b: Pre-Auth Token (PreAuthTokenStrategy)
Config: TenantSettings.security.preAuthTokenProviders[] (name, aud, jwksUri, refreshTokenEndpoint).
Auth Variable Resolution
Auth claims are exposed to workflow templates via the Auth.* namespace (e.g., {{Auth.email}}, {{Auth.sub}}), resolved by AuthVariablesResolver. Internal fields (Auth.isAuthenticated, Auth.rawToken, Auth.refreshToken) are blocked from template resolution. setSessionValue('auth.*') throws to enforce immutability of the auth store.
Role System
Organization Roles
| Role | Description |
|---|---|
HBF_ORG_ADMIN | Full organization management |
HBF_ORG_EDITOR | Edit all tenants in the organization |
HBF_ORG_VIEWER | View all tenants in the organization |
HBF_ORG_LIVE_AGENT | Live chat agent |
HBF_ORG_LIVECHAT_ADMIN | Live chat administration |
HRWIZ_EMPLOYEE | HRWiz-specific employee role |
Tenant Roles
| Role | Description |
|---|---|
HBF_TENANT_ADMIN | Full tenant management |
HBF_TENANT_EDITOR | Edit tenant resources |
HBF_TENANT_VIEWER | View tenant resources |
HBF_TENANT_LIVECHAT_ADMIN | Tenant-level live chat administration |
Pipeline Roles
| Role | Scope | Description |
|---|---|---|
admin | All pipelines | Full access to all pipeline endpoints |
client | Single pipeline | Scoped to specific pipeline_id (must match path param) |
Permission Hierarchy
- Manage > Edit > View: each higher level includes all lower permissions.
- Organization roles cascade: an org-level role applies to all tenants within that organization.
- Tenant roles are granular: assigned per-tenant for fine-grained access control.
isModeratorflag: super-admin bypass that overrides all role checks.
Guard Patterns
hbf-core (Spring Boot)
hbf-core is the central auth provider. It does not use HBFGuard (that is a NestJS pattern). Instead:
- TokenAuthenticationFilter: Intercepts all requests, extracts the Bearer token, validates signature (HS256), checks issuer (
hbf-auth) and expiry. Sets the security context. @PreAuthorizeannotations: Method-level authorization on controller endpoints. Checks roles from the security context.
NestJS Services
NestJS services combine multiple guard layers:
- HBFGuard: Validates the token by calling hbf-core
GET /users/me. Injects user into request. - JWTGuard: Local JWT validation (signature +
subclaim match). Used for service-to-service calls where the caller is trusted and the round-trip to hbf-core is unnecessary. - Role Guards: Check specific roles or permissions after authentication.
| Service | HBFGuard | JWTGuard | Role Guards | BasicAuth | Notes |
|---|---|---|---|---|---|
| hbf-core | TokenAuthenticationFilter | N/A | @PreAuthorize | No | Central auth provider |
| hbf-nlp | Yes | Yes | MemberOrg, CanEdit/Manage/ReadTenant, Moderator | No | Full RBAC |
| hbf-lcm | Yes | Yes (JWT_SUB) | AdminOrg, AnyOrg, UserGroup | No | Group-based auth |
| hbf-session-manager | Yes | No | OrgAdminOrModerator | No | |
| hbf-notifications | Yes | Yes (HBF_SERVICE role) | MemberOrg | No | Service role for S2S |
| hbf-event-publisher | Yes | Yes (JWT_SUB) | None | No | |
| hbf-client-integrations | Yes | HBFTokenGuard | OrgMember | Yes | BasicAuth on some endpoints |
| hbf-reports | Yes | No | AdminOrg, ExportGuard | No | |
| hbf-media-manager | Yes | No | AdminOrg, MemberOrg | No | |
| hbf-stats | No | No | None | No | Daemon, no HTTP API |
| hbf-data-retention | No | No | None | No | Daemon, no HTTP API |
| hbf-data-manager | Yes (read endpoints) | DMJwtGuard (delete endpoints) | OrgRole (read), none (delete) | No | Dual-guard: HBFGuard for reads, DMJwtGuard (local HS256, DM_AUTH_JWT_SECRET) for deletes |
| hbf-broadcast | No | No | None | No | Daemon |
| hbf-bot | No | No | None | No | Event-driven (Redis) |
| hbf-knowledge-manager | Yes (sync endpoints) | No | AdminOrgRoleGuard (sync endpoints) | No | Webhook endpoint (POST /webhooks/azure-blob) has no HTTP auth guard; Azure Event Grid authenticates via subscription validation handshake. Routing uses internal webhook key <accountName>/<containerName>. |
| hbf-lcg | No | JWTGuard (upstream endpoints) | None | No | Local JWT verify (JWT_SECRET + JWT_SUB); outbound Bearer to hbf-core (HBF_CORE_API_TOKEN) and hbf-lcm (HBF_LCM_TOKEN); does not issue tokens |
| hbf-console | N/A | N/A | PrivateRoute (client-side) | No | Frontend |
| hbf-webchat | N/A | N/A | N/A | No | Widget |
| hbf-core-api | N/A | N/A | N/A | No | Library (not a service) |
| helvia-rag-pipelines | No | JWTBearer | admin/client roles | No | Independent JWT system |
| semantic-doc-segmenter | No | JWTBearer | admin only | No | Independent JWT system (same pattern as rag-pipelines) |
| open-bot-framework | No | Internal JWT verify | None (binary access) | No | Self-contained DirectLine gateway; own JWT_SECRET, no hbf-core calls (planned, not yet in use) |
helvia-rag-pipelines (FastAPI)
Uses JWTBearer dependency (FastAPI Depends). Validates HS256 signature with JWT_SECRET. Checks role claim and, for client tokens, matches pipeline_id from the token against the path parameter. Entirely independent from hbf-core auth.
semantic-doc-segmenter (FastAPI)
Uses JWTBearer dependency (same pattern as helvia-rag-pipelines). Validates HS256 signature with its own JWT_SECRET. Only accepts role: "admin". No role scoping, no token expiry enforcement. All endpoints require auth. Entirely independent from hbf-core auth.
hbf-knowledge-manager (NestJS)
Uses a dual-endpoint auth model:
POST /sync/org/:orgId/integrations/:integrationId/knowledge-bases/:knowledgeBaseId/full: Protected byHBFGuard(class-level) +AdminOrgRoleGuard(method-level). Standard pattern: HBFGuard calls hbf-coreGET /users/me, then AdminOrgRoleGuard enforcesHBF_ORG_ADMINmembership for the org in the path.POST /webhooks/azure-blob: No HTTP auth guard. This endpoint is called by Azure Event Grid, which authenticates the subscription via a one-time validation handshake (Event Grid sends aSubscriptionValidationevent; the service echoes back thevalidationCode). Subsequent event deliveries carry no bearer token. Authorization is implicit: events are routed only to integrations whose stored webhook key matches the incoming<accountName>/<containerName>key derived from the event payload.
No token issuance. No JWTGuard. The internal webhook routing key format is <accountName>/<containerName> (both lowercased), built by buildAzureBlobWebhookKey.
hbf-lcg (NestJS)
hbf-lcg is a live-chat gateway service. It does not issue tokens.
- Upstream endpoints (
gateway-upstream.controller.ts): all routes are protected byJWTGuard. Validation is local: checks the HS256 signature usingJWT_SECRETand verifies thesubclaim matches the configuredJWT_SUBenv var. No call to hbf-core. - Downstream Helvia events: incoming events from Helvia are also verified via JWT (
DownstreamHelviaService), using the same local verification approach. - Outbound to hbf-core: requests carry
Authorization: Bearer <HBF_CORE_API_TOKEN>(pre-provisioned service token). - Outbound to hbf-lcm: requests carry
Authorization: Bearer <HBF_LCM_TOKEN>(pre-provisioned service token).
hbf-client-integrations (BasicAuth)
This service is a special case. Some endpoints use HTTP Basic Authentication (username/password) instead of or in addition to Bearer tokens. This is configured per-endpoint for specific integration requirements. Other endpoints use the standard HBFGuard + HBFTokenGuard pattern.
Public Endpoints (No Auth Required)
| Service | Endpoint | Purpose |
|---|---|---|
| hbf-core | / | Root |
| hbf-core | /public/** | Public resources (bot deployments, etc.) |
| hbf-core | /login/** | Authentication endpoints |
| hbf-core | /swagger-ui/** | API documentation |
| hbf-core | /v3/api-docs/** | OpenAPI spec |
| hbf-core | /actuator/health/** | Health checks |
| hbf-webchat | GET /public/bot-deployments/{deploymentId} | Bot deployment config (fetched from hbf-core public endpoint) |
| helvia-rag-pipelines | GET / | Root info endpoint |
Security Configuration
| Property | Value |
|---|---|
| Signing algorithm | HS256 (HMAC-SHA256), all token types |
| Login token secret | app.security.token.secret (Base64-encoded before signing) |
| API token secret | APP_SECURITY_API_DEPLOYMENT_TOKEN_SECRET (separate from login secret) |
| Pipeline JWT secret | JWT_SECRET (separate from hbf-core secrets) |
| DM JWT secret | DM_AUTH_JWT_SECRET (separate from all other secrets; shared between hbf-data-manager and its delete callers) |
| Session model | Stateless (no server-side sessions) |
| CSRF protection | Disabled (not needed with JWT-based auth) |
| Account lockout | After 10 failed login attempts |
| Login token expiry | 24 hours |
| Refresh token expiry | 7 days |
| Client-side refresh interval | Every 1 hour |
| Direct Line token expiry | ~15 minutes (auto-renewed) |
| Service token (CORE_TOKEN) expiry | Long-lived |
| API token expiry | No expiry |
| Pipeline JWT expiry | No expiry by default |
Token Refresh
Client-Side (hbf-console)
The console runs a periodic check every 1 hour. When the User JWT is approaching expiry, the console sends the refresh token to POST /login/refresh. hbf-core validates the refresh token (type must be "refresh_token", signature and expiry valid) and issues a new User JWT. The console updates localStorage with the new token.
If the refresh token itself has expired (7-day window passed), hbf-core returns 401. The console clears all stored auth data and redirects the user to the login page.
Server-Side (hbf-core)
POST /login/refresh accepts a refresh token in the Authorization header. It validates the token, extracts the user information from the claims, and issues a fresh User JWT with a new 24-hour expiry. The refresh token itself is not rotated (the same refresh token remains valid until its 7-day expiry).
Security Notes
- Four separate signing secrets exist across the platform: one for login/refresh tokens, one for API deployment tokens, one for pipeline JWTs, and one for DM JWTs (
DM_AUTH_JWT_SECRET). These are independent and must be configured separately. - No token rotation for CORE_TOKEN: service tokens are long-lived with no automatic rotation mechanism. Rotation requires redeployment with a new token value.
- No refresh token rotation: the refresh token is reused until expiry rather than being rotated on each use. A compromised refresh token remains valid for its full 7-day lifetime.
- Pipeline JWTs have no expiry by default: tokens created via
POST /admin/tokenare valid indefinitely unless theJWT_SECRETis rotated. - API tokens have no expiry: programmatic access tokens persist until manually revoked.
- HBFGuard adds latency: every authenticated request in NestJS services makes a synchronous call to hbf-core
GET /users/me. Services using JWTGuard avoid this round-trip at the cost of not having real-time user state. - JWT issuer claim: all hbf-core tokens use issuer
hbf-auth. TokenAuthenticationFilter validates this claim.