Authentication & Authorization
4. Authentication & Authorization¶
Document status: Draft v0.1 Depends on: Hippo sec2 (auth middleware stub, actor field), Hippo sec6 (provenance event model), Aperture sec2 (auth model / token expectations) Feeds into: Bridge sec2 (architecture), Bridge sec3 (unified API), Aperture sec2 (auth integration), all component deployment docs
4.1 Design Philosophy¶
Bridge owns all authentication and authorization for the BASS platform. Individual components
(Hippo, Cappella) are auth-unaware by design — they accept a validated actor identity from
their transport layer and trust it unconditionally. Bridge sits in front of component REST APIs
as the single enforcement point:
-
Centralized auth, decentralized enforcement — Bridge validates credentials and injects a verified actor identity. Component auth middleware stubs (e.g., Hippo's
AuthMiddlewareABC) are replaced by Bridge-aware implementations that extract the validated identity from request context rather than performing their own credential checks. -
No component user stores — Components never maintain user databases, session tables, or credential stores. All identity resolution flows through Bridge.
-
SDK bypass is intentional — When components are used in SDK mode (e.g., local
HippoClientwith SQLite), there is no Bridge and no auth. This is the correct posture for single-user local deployments. Auth applies only at the transport layer boundary. -
Standards-based — Bridge uses widely adopted standards (OAuth 2.0, JWT, RBAC) to minimize custom security code and maximize interoperability with institutional identity providers.
4.2 Authentication Standards¶
4.2.1 Primary: OAuth 2.0 + JWT¶
Bridge uses OAuth 2.0 as the authorization framework and JWT (RFC 7519) as the token format. This combination was chosen because:
- OAuth 2.0 is the dominant standard in both enterprise and research computing environments
- JWTs are stateless and can carry claims (roles, scopes) that downstream components inspect without calling back to Bridge
- Broad library support in Python (
PyJWT,authlib) and every other language
Supported OAuth 2.0 flows:
| Flow | Use Case | Client Type |
|---|---|---|
| Authorization Code + PKCE | Web portal (Aperture web UI) | Public client (browser) |
| Device Code (RFC 8628) | CLI tools (Aperture CLI, bass command) |
Public client (terminal) |
| Client Credentials | Service-to-service (Cappella → Hippo, pipeline agents) | Confidential client |
SAML 2.0 is not natively supported. Institutions requiring SAML can federate through their IdP's OAuth 2.0 bridge (e.g., Shibboleth with OAuth proxy, Azure AD). This avoids the complexity of maintaining a SAML SP implementation while remaining compatible with the academic identity ecosystem.
4.2.2 Identity Provider Integration¶
Bridge acts as an OAuth 2.0 client to external Identity Providers (IdPs), not as an IdP itself. Supported IdP configurations:
| Provider Type | Examples | Protocol |
|---|---|---|
| Institutional IdP | Azure AD, Okta, Auth0, Keycloak | OIDC (OpenID Connect) |
| Academic federation | CILogon, InCommon | OIDC via CILogon proxy |
| Local dev/test | Bridge built-in provider | OAuth 2.0 (local-only) |
┌────────────┐ ┌─────────────────┐ ┌────────────────┐
│ Aperture │────▶│ Bridge │────▶│ External IdP │
│ (CLI/Web) │◀────│ (OAuth Client) │◀────│ (OIDC Server) │
└────────────┘ └────────┬────────┘ └────────────────┘
│
┌─────────▼─────────┐
│ JWT issued by │
│ Bridge (signed) │
└───────────────────┘
After external authentication, Bridge issues its own signed JWT with BASS-specific claims. Components never interact with external IdP tokens directly.
4.2.3 Token Structure¶
Bridge-issued JWTs contain the following claims:
| Claim | Type | Description |
|---|---|---|
sub |
string | Unique user identifier (Bridge user ID, UUID) |
iss |
string | bass-bridge (fixed issuer) |
aud |
string | bass-platform (or component-specific audience for scoped tokens) |
exp |
int | Expiry timestamp (UTC epoch) |
iat |
int | Issued-at timestamp |
jti |
string | Unique token ID (for revocation checking) |
bass:actor |
string | The actor identity string passed to component actor fields |
bass:roles |
string[] | RBAC roles assigned to this user (see §4.3) |
bass:scopes |
string[] | OAuth scopes granted in this session |
bass:org |
string | Organization/tenant identifier (multi-tenant deployments) |
The bass:actor claim is the canonical link between Bridge auth and Hippo provenance — it
becomes the actor field on all provenance events, ensuring an unbroken audit trail from
login through every data mutation.
4.3 Role-Based Access Control (RBAC)¶
4.3.1 Role Model¶
Bridge implements a flat RBAC model (no role hierarchy in v0.1) with the following predefined roles:
| Role | Description | Typical Persona |
|---|---|---|
admin |
Full platform access; manage users, roles, and configuration | Platform administrator |
project_lead |
Full data access within assigned projects; manage project membership | PI, lab manager |
analyst |
Read/write entities and run pipelines within assigned projects | Bioinformatician, data scientist |
viewer |
Read-only access to entities and pipeline results within assigned projects | Collaborator, auditor |
service |
Machine identity for pipelines and automated processes | Cappella runner, ingestion agent |
Roles are assigned per-user. Project scoping (which projects a user can access) is a separate dimension — see §4.3.3.
4.3.2 Permission Matrix¶
Permissions map roles to operations on BASS resources:
| Operation | admin |
project_lead |
analyst |
viewer |
service |
|---|---|---|---|---|---|
| Entity CRUD (create, read, update) | ✅ | ✅ (own projects) | ✅ (own projects) | read only | ✅ (scoped) |
| Entity availability change | ✅ | ✅ (own projects) | ❌ | ❌ | ❌ |
| Schema management (upload, migrate) | ✅ | ❌ | ❌ | ❌ | ❌ |
| Pipeline execution (submit, cancel) | ✅ | ✅ | ✅ | ❌ | ✅ |
| Pipeline results (read) | ✅ | ✅ | ✅ | ✅ | ✅ (own) |
| Provenance read (history, audit log) | ✅ | ✅ (own projects) | ✅ (own projects) | ✅ (own projects) | ❌ |
| User/role management | ✅ | ❌ | ❌ | ❌ | ❌ |
| API key management (create, revoke) | ✅ | own keys only | own keys only | ❌ | N/A |
| Reference data install | ✅ | ✅ | ❌ | ❌ | ✅ |
4.3.3 Project Scoping¶
Non-admin roles are scoped to projects — a lightweight grouping concept managed by Bridge:
- A project has a name, description, and a set of member-role assignments
- Entity types in Hippo can optionally declare a
projectfield; Bridge enforces that non-admin users can only access entities belonging to their assigned projects - When a project field is not declared in the schema, project scoping is disabled and all authenticated users with the appropriate role can access all entities
adminusers bypass project scoping entirely
┌──────────────────────────────────────────────┐
│ Bridge Project: "Genomics Lab A" │
│ │
│ Members: │
│ alice@uni.edu → project_lead │
│ bob@uni.edu → analyst │
│ pipeline-agent → service │
│ │
│ Scoped to: entities where project = │
│ "genomics-lab-a" │
└──────────────────────────────────────────────┘
4.3.4 Future: Hierarchical Roles and Custom Permissions¶
v0.1 uses a flat role model for simplicity. Future versions may add:
- Role hierarchy (e.g., project_lead inherits all analyst permissions)
- Custom permission definitions per deployment
- Attribute-based access control (ABAC) for fine-grained entity-level rules
These are explicitly deferred. The flat model covers the common cases for academic and small institutional deployments.
4.4 Token Management and Session Handling¶
4.4.1 Token Lifecycle¶
┌──────────┐ authenticate ┌─────────┐ issue tokens ┌──────────────┐
│ Client │──────────────▶│ Bridge │───────────────▶│ Access Token │
│ (Aperture)│ │ Auth │ │ (JWT, 15min) │
└──────────┘ │ Service │ ├──────────────┤
│ │───────────────▶│Refresh Token │
└─────────┘ │ (opaque, 7d) │
└──────────────┘
| Token Type | Format | Lifetime | Storage |
|---|---|---|---|
| Access token | JWT (signed, not encrypted) | 15 minutes (configurable) | In-memory only; never persisted by client |
| Refresh token | Opaque string (UUID) | 7 days (configurable) | Client: ~/.bass/tokens.json (encrypted via OS keyring). Bridge: hashed in token store |
| API key | Opaque string (prefixed bass_) |
No expiry (revocable) | Client: environment variable or config file. Bridge: hashed in key store |
4.4.2 Token Refresh¶
Aperture (and other clients) refresh tokens automatically before access token expiry:
- Client detects access token will expire within 60 seconds
- Client sends refresh token to
POST /bridge/auth/token/refresh - Bridge validates refresh token, issues new access + refresh token pair
- Old refresh token is invalidated (rotation)
Refresh token rotation ensures that a leaked refresh token can only be used once before detection. If a rotated-out refresh token is presented, Bridge revokes the entire token family and forces re-authentication.
4.4.3 Token Revocation¶
- Individual tokens:
POST /bridge/auth/token/revoke(byjtior refresh token) - All user tokens:
POST /bridge/auth/users/{userId}/revoke-all(admin only) - Revocation is checked via a short-lived revocation cache (in-memory set of revoked
jtivalues, synced from the token store). Because access tokens are short-lived (15min), the revocation window is bounded.
4.5 Integration with Hippo Provenance¶
Bridge auth integrates with Hippo's provenance system through the actor field:
4.5.1 Actor Identity Flow¶
┌──────────┐ ┌─────────┐ ┌──────────────┐ ┌─────────────┐
│ Client │────▶│ Bridge │────▶│ Hippo REST │────▶│ Provenance │
│ │ │ │ │ │ │ Event │
│ JWT with │ │ Extract │ │ auth.py uses │ │ │
│ bass:actor│ │ actor │ │ actor from │ │ actor = │
│ │ │ claim │ │ request ctx │ │ "alice@ │
│ │ │ │ │ │ │ uni.edu" │
└──────────┘ └─────────┘ └──────────────┘ └─────────────┘
- Bridge extracts the
bass:actorclaim from the JWT - Bridge injects the actor identity into the proxied request (via
X-Bass-Actorheader or request body override) - Hippo's
AuthMiddlewareimplementation (Bridge-aware) reads the injected actor and uses it for all provenance events - The actor value in provenance is always the authenticated identity — callers cannot override it when Bridge is active
4.5.2 Service Account Actors¶
Service accounts (role service) produce provenance events with actor values like
service:cappella-runner or service:ingestion-agent-01. These are distinguishable from
human actors by the service: prefix convention.
4.5.3 Provenance Query Authorization¶
Bridge enforces that provenance history queries respect project scoping:
- Users can only view provenance events for entities in their assigned projects
- admin users can view all provenance events
- The viewer role can read provenance but not trigger mutations
4.6 API Key Management¶
API keys provide long-lived authentication for non-interactive use cases (scripts, pipeline agents, CI/CD integrations).
4.6.1 Key Structure¶
Keys are prefixed with bass_live_ or bass_test_ to prevent accidental cross-environment
use. The prefix is not secret — it aids in key identification and rotation auditing.
4.6.2 Key Lifecycle¶
| Operation | Endpoint | Who Can Do It |
|---|---|---|
| Create key | POST /bridge/auth/api-keys |
admin, or user creating own key (project_lead, analyst) |
| List keys | GET /bridge/auth/api-keys |
Own keys, or all keys for admin |
| Revoke key | DELETE /bridge/auth/api-keys/{keyId} |
Key owner or admin |
| Rotate key | POST /bridge/auth/api-keys/{keyId}/rotate |
Key owner or admin |
Keys are created with: - A human-readable label (e.g., "Cappella pipeline runner") - An optional expiry date (no expiry by default) - An assigned role (cannot exceed the creating user's role) - Optional project scope (limits key to specific projects)
4.6.3 Key Authentication Flow¶
When a request arrives with an API key (via Authorization: Bearer bass_live_... or
X-Api-Key header):
- Bridge hashes the key and looks it up in the key store
- If found and not revoked/expired, Bridge constructs an equivalent JWT claim set from the key's metadata (role, scopes, actor identity)
- Downstream processing is identical to JWT-authenticated requests — components see no difference
4.7 Service-to-Service Authentication¶
Internal communication between BASS components (e.g., Cappella calling Hippo to register pipeline outputs) uses the Client Credentials OAuth 2.0 flow:
4.7.1 Service Registration¶
Each component that needs to call other components registers as an OAuth client with Bridge:
| Service | Client ID | Default Role | Purpose |
|---|---|---|---|
| Cappella | cappella-engine |
service |
Register pipeline outputs in Hippo |
| Aperture (web portal) | aperture-portal |
N/A (acts on behalf of users) | Backend-for-frontend |
| Custom pipeline agents | Per-agent registration | service |
Automated data ingestion |
4.7.2 Internal Token Flow¶
┌──────────┐ client_credentials ┌─────────┐ service JWT ┌───────┐
│ Cappella │────────────────────▶│ Bridge │─────────────▶│ Hippo │
│ │ (client_id + │ Auth │ │ │
│ │ client_secret) │ │ │ │
└──────────┘ └─────────┘ └───────┘
Service tokens are short-lived (5 minutes) and scoped to the specific operations the service needs. Service secrets are provisioned at deployment time and stored in the platform's secret manager (e.g., AWS Secrets Manager, Vault, or environment variables for local deployments).
4.8 Deployment Configuration¶
4.8.1 Bridge Auth Configuration (bridge.yaml)¶
auth:
# JWT signing
jwt:
algorithm: RS256 # RS256 recommended; HS256 for local dev
signing_key: ${BRIDGE_JWT_KEY} # Path to PEM private key or inline HMAC secret
public_key: ${BRIDGE_JWT_PUB_KEY} # Path to PEM public key (RS256 only)
access_token_ttl: 900 # seconds (15 min default)
refresh_token_ttl: 604800 # seconds (7 day default)
# Identity provider
idp:
provider: oidc # oidc | local
issuer_url: https://idp.uni.edu # OIDC discovery endpoint
client_id: ${BRIDGE_OIDC_CLIENT}
client_secret: ${BRIDGE_OIDC_SECRET}
# Token storage (refresh tokens, revocations)
token_store:
backend: sqlite # sqlite | postgresql | redis
connection: ${BRIDGE_TOKEN_DB}
# API key storage
api_key_store:
backend: sqlite # sqlite | postgresql
connection: ${BRIDGE_APIKEY_DB}
# Built-in local provider (dev/test only)
local_provider:
enabled: false
users:
- username: admin
password: ${BRIDGE_LOCAL_ADMIN_PW}
roles: [admin]
- username: testuser
password: ${BRIDGE_LOCAL_TEST_PW}
roles: [analyst]
4.8.2 Deployment Tiers¶
| Tier | IdP | JWT Algorithm | Token Store | Key Store |
|---|---|---|---|---|
| Local dev | Built-in local | HS256 | SQLite | SQLite |
| Team server | Keycloak / Auth0 | RS256 | SQLite or PostgreSQL | SQLite or PostgreSQL |
| Enterprise | Institutional OIDC (Azure AD, Okta) | RS256 | PostgreSQL or Redis | PostgreSQL |
All tiers use the same Bridge codebase. Tier is determined entirely by bridge.yaml
configuration.
4.9 Component Auth Integration Points¶
This section documents how each BASS component integrates with Bridge auth:
4.9.1 Hippo¶
- Middleware replacement: Hippo's
AuthMiddlewareABC (defined inhippo/rest/auth.py) receives a Bridge-aware implementation that extractsX-Bass-ActorandX-Bass-Rolesheaders from proxied requests authenticate(): Returns the actor identity fromX-Bass-Actor(validated by Bridge before the request reaches Hippo)authorize(): Checks the operation against the role permissions inX-Bass-Roles. Because Bridge has already validated the JWT and injected headers, Hippo does not need JWT libraries or signing keys- SDK mode: No change —
HippoClientacceptsactoras a string parameter with no validation, as designed in Hippo sec2 §2.7
4.9.2 Cappella¶
- Pipeline submission: Cappella validates the caller's JWT (forwarded by Bridge) to ensure
the
analystorservicerole before accepting pipeline execution requests - Output registration: Cappella uses its service credential (Client Credentials flow) to write pipeline results back to Hippo through Bridge
4.9.3 Aperture¶
- CLI: Uses Device Code flow. On first use,
bass loginopens a browser for authentication. Tokens stored in~/.bass/tokens.json(encrypted via OS keyring) - Web portal: Uses Authorization Code + PKCE flow. Session managed by Aperture's BFF (backend-for-frontend) server
- Token refresh: Handled transparently by Aperture's
backends/auth.pymodule (see Aperture sec2 §2.8)
4.10 Security Considerations¶
| Concern | Mitigation |
|---|---|
| JWT signing key compromise | Use RS256 with hardware-backed keys in production; rotate annually |
| Refresh token theft | Rotation on every use; stolen rotated token triggers family revocation |
| API key leakage | Prefixed keys for environment detection; hash-only storage; revocation API |
| Privilege escalation | API keys cannot exceed creator's role; no self-promotion |
| Cross-site attacks (web portal) | PKCE for all browser flows; SameSite=Strict cookies; CORS allowlist |
| Service credential compromise | Short-lived service tokens (5min); secrets in dedicated secret manager |
| Actor identity spoofing | X-Bass-Actor header only accepted from Bridge's internal network; components reject it from external sources |
4.11 Open Questions¶
| Question | Priority | Notes |
|---|---|---|
| Should Bridge support LDAP directly for institutions without OIDC? | Medium | Current position: no — use OIDC proxy. May revisit if adoption blocked |
| Fine-grained entity-type permissions (e.g., can edit Sample but not Subject) | Medium | Deferred to v0.2; current RBAC is operation-level, not entity-type-level |
| Multi-tenant isolation model (shared vs. siloed token stores) | High | Relevant for SaaS deployment; deferred until multi-tenancy is scoped |
| Offline token validation (JWT verification without Bridge connectivity) | Low | JWTs are self-contained by design; document the offline-capable pattern |
| Audit log for auth events (login, token refresh, key creation) | Medium | Should feed into Bridge's own event log, separate from Hippo provenance |