Skip to main content

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

TokenIssued ByPayloadSigningExpiryValidated By
User JWThbf-core POST /login (also /login/google, /login/microsoft, /login/teams, /login/integration)sub (user ID), email, name, type ("login"), organizationId, authorities, iat, expHS256, secret: app.security.token.secret (Base64-encoded)24h (86400s)hbf-core TokenAuthenticationFilter; NestJS HBFGuard (calls GET /users/me)
Refresh Tokenhbf-core (issued alongside User JWT)Same as User JWT, type = "refresh_token"Same as User JWT7 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-livedhbf-core TokenAuthenticationFilter
Pipeline JWThelvia-rag-pipelines POST /admin/tokenrole ("admin" / "client"), pipeline_id (optional), iatHS256, secret: JWT_SECRET (separate from hbf-core)No expiry by defaulthelvia-rag-pipelines (local validation, no call to hbf-core)
DM JWTCaller shares DM_AUTH_JWT_SECRET (no issuance endpoint in hbf-data-manager)Any valid payloadHS256, secret: DM_AUTH_JWT_SECRET (separate from all other secrets)Not enforcedhbf-data-manager DMJwtGuard (local validation, no call to hbf-core)
API Tokenhbf-core POST /api-tokens (moderator-only)sub (token ID), name, type ("api_token"), role, organizationId, isModeratorHS256, secret: APP_SECURITY_API_DEPLOYMENT_TOKEN_SECRET (separate from login secret)Long-lived (no expiry)hbf-core TokenAuthenticationFilter
Direct Line TokenExternal 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:

  1. Extract Authorization: Bearer <token> from the request header.
  2. Call hbf-core GET /users/me with the token.
  3. hbf-core validates the token via TokenAuthenticationFilter (checks signature, issuer hbf-auth, expiry).
  4. hbf-core returns user data including roles and organization membership.
  5. HBFGuard injects the user object into request.user.
  6. 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:

  • HBFGuard on read endpoints (GET /orgs/:orgId/**): delegates to hbf-core GET /users/me and enforces org-level role membership.
  • DMJwtGuard on delete endpoints (DELETE /chat-sessions/**, POST /chat-sessions/interactions/bulk-delete): validates the token locally against DM_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:

  1. window.HBF_retrieveUserId() function (if defined by host page).
  2. URL query parameter ?hsh=.
  3. Previously stored value in localStorage.
  4. 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_id in the token must match the pipeline_id path 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

RoleDescription
HBF_ORG_ADMINFull organization management
HBF_ORG_EDITOREdit all tenants in the organization
HBF_ORG_VIEWERView all tenants in the organization
HBF_ORG_LIVE_AGENTLive chat agent
HBF_ORG_LIVECHAT_ADMINLive chat administration
HRWIZ_EMPLOYEEHRWiz-specific employee role

Tenant Roles

RoleDescription
HBF_TENANT_ADMINFull tenant management
HBF_TENANT_EDITOREdit tenant resources
HBF_TENANT_VIEWERView tenant resources
HBF_TENANT_LIVECHAT_ADMINTenant-level live chat administration

Pipeline Roles

RoleScopeDescription
adminAll pipelinesFull access to all pipeline endpoints
clientSingle pipelineScoped 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.
  • isModerator flag: 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.
  • @PreAuthorize annotations: Method-level authorization on controller endpoints. Checks roles from the security context.

NestJS Services

NestJS services combine multiple guard layers:

  1. HBFGuard: Validates the token by calling hbf-core GET /users/me. Injects user into request.
  2. JWTGuard: Local JWT validation (signature + sub claim match). Used for service-to-service calls where the caller is trusted and the round-trip to hbf-core is unnecessary.
  3. Role Guards: Check specific roles or permissions after authentication.
ServiceHBFGuardJWTGuardRole GuardsBasicAuthNotes
hbf-coreTokenAuthenticationFilterN/A@PreAuthorizeNoCentral auth provider
hbf-nlpYesYesMemberOrg, CanEdit/Manage/ReadTenant, ModeratorNoFull RBAC
hbf-lcmYesYes (JWT_SUB)AdminOrg, AnyOrg, UserGroupNoGroup-based auth
hbf-session-managerYesNoOrgAdminOrModeratorNo
hbf-notificationsYesYes (HBF_SERVICE role)MemberOrgNoService role for S2S
hbf-event-publisherYesYes (JWT_SUB)NoneNo
hbf-client-integrationsYesHBFTokenGuardOrgMemberYesBasicAuth on some endpoints
hbf-reportsYesNoAdminOrg, ExportGuardNo
hbf-media-managerYesNoAdminOrg, MemberOrgNo
hbf-statsNoNoNoneNoDaemon, no HTTP API
hbf-data-retentionNoNoNoneNoDaemon, no HTTP API
hbf-data-managerYes (read endpoints)DMJwtGuard (delete endpoints)OrgRole (read), none (delete)NoDual-guard: HBFGuard for reads, DMJwtGuard (local HS256, DM_AUTH_JWT_SECRET) for deletes
hbf-broadcastNoNoNoneNoDaemon
hbf-botNoNoNoneNoEvent-driven (Redis)
hbf-knowledge-managerYes (sync endpoints)NoAdminOrgRoleGuard (sync endpoints)NoWebhook 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-lcgNoJWTGuard (upstream endpoints)NoneNoLocal 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-consoleN/AN/APrivateRoute (client-side)NoFrontend
hbf-webchatN/AN/AN/ANoWidget
hbf-core-apiN/AN/AN/ANoLibrary (not a service)
helvia-rag-pipelinesNoJWTBeareradmin/client rolesNoIndependent JWT system
semantic-doc-segmenterNoJWTBeareradmin onlyNoIndependent JWT system (same pattern as rag-pipelines)
open-bot-frameworkNoInternal JWT verifyNone (binary access)NoSelf-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 by HBFGuard (class-level) + AdminOrgRoleGuard (method-level). Standard pattern: HBFGuard calls hbf-core GET /users/me, then AdminOrgRoleGuard enforces HBF_ORG_ADMIN membership 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 a SubscriptionValidation event; the service echoes back the validationCode). 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 by JWTGuard. Validation is local: checks the HS256 signature using JWT_SECRET and verifies the sub claim matches the configured JWT_SUB env 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)

ServiceEndpointPurpose
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-webchatGET /public/bot-deployments/{deploymentId}Bot deployment config (fetched from hbf-core public endpoint)
helvia-rag-pipelinesGET /Root info endpoint

Security Configuration

PropertyValue
Signing algorithmHS256 (HMAC-SHA256), all token types
Login token secretapp.security.token.secret (Base64-encoded before signing)
API token secretAPP_SECURITY_API_DEPLOYMENT_TOKEN_SECRET (separate from login secret)
Pipeline JWT secretJWT_SECRET (separate from hbf-core secrets)
DM JWT secretDM_AUTH_JWT_SECRET (separate from all other secrets; shared between hbf-data-manager and its delete callers)
Session modelStateless (no server-side sessions)
CSRF protectionDisabled (not needed with JWT-based auth)
Account lockoutAfter 10 failed login attempts
Login token expiry24 hours
Refresh token expiry7 days
Client-side refresh intervalEvery 1 hour
Direct Line token expiry~15 minutes (auto-renewed)
Service token (CORE_TOKEN) expiryLong-lived
API token expiryNo expiry
Pipeline JWT expiryNo 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/token are valid indefinitely unless the JWT_SECRET is 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.