Access control
Version: 1.0
Last Reviewed: 2026-03-07
Review Cadence: Quarterly
Classification: Internal / Customer-Shareable
Owner: Perceive8 Engineering & Security
Table of Contents
- Overview
- Authentication Methods
- Authorization Model
- API Key Lifecycle
- Session Management
- Admin Access Controls
- WebSocket & Streaming Access Controls
- Third-Party Access
- Access Control Matrix
- Principle of Least Privilege
- Known Limitations & Planned Improvements
Overview
Perceive8 implements a layered access control model combining multiple authentication methods with role-based, ownership-based, and scope-based authorization. The model follows the principle of least privilege: every identity receives only the minimum permissions required for its intended use case.
Access Control Layers
┌─────────────────────────────────────────────┐
│ Kong API Gateway (Rate Limiting, Routing) │
├─────────────────────────────────────────────┤
│ Authentication Layer (JWT / API Key / OAuth)│
├─────────────────────────────────────────────┤
│ Authorization Layer (Role + Ownership + │
│ Scope Enforcement) │
├─────────────────────────────────────────────┤
│ Row-Level Security (Supabase/PostgreSQL) │
└─────────────────────────────────────────────┘
Every request passes through all layers. A failure at any layer results in request rejection.
Authentication Methods
1. JWT Bearer Tokens
Use Case: Dashboard users, browser-based sessions, UI-only endpoints.
| Property | Value |
|---|---|
| Issuer | Supabase Auth |
| Algorithm | HS256 (HMAC-SHA256) |
| Audience | authenticated |
| Transport | Authorization: Bearer <token> header |
| Expiry | Enforced; expired tokens are rejected |
| Refresh | Handled by Supabase Auth client SDK |
| Validation | Signature verification, audience check, expiry check |
Authentication Flow:
- User authenticates via Supabase Auth (email/password, OAuth provider, magic link).
- Supabase issues a JWT with
sub(user ID),aud(authenticated), andexpclaims. - Client includes the JWT in the
Authorization: Bearerheader on every request. - The API validates the JWT signature using the shared secret, checks the
audclaim, and verifies the token has not expired. - The
subclaim is extracted as the authenticated user ID for downstream authorization.
UI-Only Endpoints:
Some endpoints are restricted to JWT authentication only (no API keys) using the verify_jwt_only dependency. This is used for endpoints that should only be accessible from the Perceive8 dashboard, not from programmatic integrations.
2. API Keys
Use Case: Server-to-server integrations, SDK usage, programmatic access.
| Property | Value |
|---|---|
| Format | pk_live_<random> (prefix-based) |
| Storage | bcrypt hash (cost=12); plaintext never stored |
| Lookup | Prefix-based database lookup, then bcrypt verification |
| Transport | Authorization: Bearer pk_live_... header |
| Expiry | Optional expires_at field; enforced if set |
| Scopes | Per-key scope list restricting permitted operations |
| Rate Limiting | Per-key rate_limit_rpm (requests per minute) |
| Tracking | last_used_at updated on every use |
Authentication Flow:
- User creates an API key via the dashboard or API. The plaintext key is returned once and never stored.
- The key prefix (
pk_live_<first-N-chars>) and bcrypt hash are stored in the database. - On each request, the API extracts the prefix from the provided key, looks up the matching record, and verifies the full key against the stored bcrypt hash.
- If the key has an
expires_atvalue and it has passed, the request is rejected. - The key's scopes are loaded for downstream authorization checks.
last_used_atis updated to the current timestamp.
3. OAuth Client Credentials
Use Case: Third-party application integrations.
| Property | Value |
|---|---|
| Status | Alpha |
| Grant Type | Client Credentials (client_credentials) |
| Client Secret | bcrypt-hashed; plaintext shown once at creation |
| Token Type | JWT |
| Scope | Defined per OAuth client |
Authentication Flow:
- Developer registers an OAuth client via the API, receiving a
client_idandclient_secret. - The client exchanges credentials at the token endpoint for a JWT access token.
- The access token is used in the
Authorization: Bearerheader for subsequent requests.
Note: OAuth Client Credentials is in Alpha status. The implementation is functional but may change before GA.
4. Query Parameter Tokens
Use Case: SSE/EventSource endpoints where Authorization headers cannot be set.
| Property | Value |
|---|---|
| Transport | ?token=<jwt> query parameter |
| Scope | SSE/EventSource streaming endpoints only |
| Validation | Same JWT validation as Bearer token |
This method exists because the browser EventSource API does not support custom headers. The token is validated identically to a Bearer JWT.
Authorization Model
Role-Based Access Control (RBAC)
Perceive8 implements a two-tier role model:
| Role | Assignment | Capabilities |
|---|---|---|
| Regular User | Default for all authenticated users | CRUD on own resources, API key management, webhook configuration, streaming |
| Admin | Explicit allowlist configuration | All regular user capabilities + admin dashboard, user management, system configuration, platform analytics |
Admin status is determined by checking the authenticated user's ID against a configured admin allowlist. This is enforced by authorization middleware on admin-only endpoints.
Resource Ownership
Every data resource in Perceive8 is associated with an owner (the user who created it). Ownership is verified on every resource access:
| Resource | Ownership Field | Verification |
|---|---|---|
| Conversations | user_id |
Authenticated user ID must match user_id |
| Analyses | user_id (via conversation) |
Ownership verified through parent conversation |
| Transcripts | user_id (via conversation) |
Ownership verified through parent conversation |
| API Keys | user_id |
User can only manage their own keys |
| Webhooks | user_id |
User can only manage their own webhooks |
| OAuth Clients | user_id |
User can only manage their own clients |
| Streaming Sessions | user_id |
User can only access their own streams |
| Scenarios | user_id |
User can only manage their own scenarios |
| Reports | user_id |
User can only access their own reports |
| Speakers / Voiceprints | user_id |
User can only manage their own speakers |
| Alerts | user_id |
User can only manage their own alerts |
| Billing / Subscriptions | user_id |
User can only view their own billing data |
Admin Override: Admin users can access resources across all users through admin-specific endpoints (e.g., /admin/users, /admin/conversations).
Scope-Based Access Control
API keys carry a list of scopes that restrict which operations they can perform. Scopes are enforced by the require_scope() dependency on protected endpoints.
| Scope | Permits |
|---|---|
conversations:read |
List and retrieve conversations, transcripts, analyses |
conversations:write |
Create, update, delete conversations; upload audio |
streaming:read |
Read streaming session data |
streaming:write |
Create and manage streaming sessions |
webhooks:read |
List and retrieve webhook configurations |
webhooks:write |
Create, update, delete webhooks |
scenarios:read |
List and retrieve scenarios |
scenarios:write |
Create, update, delete scenarios |
reports:read |
List and retrieve reports |
reports:write |
Generate reports |
speakers:read |
List and retrieve speaker profiles |
speakers:write |
Create, update, delete speaker profiles |
alerts:read |
List and retrieve alerts |
alerts:write |
Create, update, delete alerts |
billing:read |
View billing and subscription information |
admin:* |
Full admin access (admin users only) |
Scope Enforcement:
- If an API key does not include a required scope, the request is rejected with
403 Forbidden. - JWT-authenticated users (dashboard) are not subject to scope restrictions — scopes apply only to API keys.
- Scopes are set at key creation time and cannot be modified after creation (a new key must be created).
Row-Level Security (RLS)
Supabase PostgreSQL enforces Row-Level Security policies at the database layer:
- RLS policies ensure that database queries only return rows belonging to the authenticated user.
- This provides defense-in-depth: even if application-level ownership checks were bypassed, the database would still enforce access boundaries.
- RLS policies are defined per table and enforced on all SELECT, INSERT, UPDATE, and DELETE operations.
API Key Lifecycle
Creation
User Request → API validates user identity → Generate random key →
Compute bcrypt hash → Store hash + prefix + metadata → Return plaintext key (once)
| Step | Details |
|---|---|
| Generation | Cryptographically random key with pk_live_ prefix |
| Hashing | bcrypt with cost factor 12 |
| Storage | Hash, prefix, user_id, scopes, rate_limit_rpm, expires_at |
| Response | Plaintext key returned once; user must store it securely |
Usage
| Check | Action on Failure |
|---|---|
| Prefix lookup | 401 Unauthorized — key not found |
| bcrypt verification | 401 Unauthorized — invalid key |
Expiry check (expires_at) |
401 Unauthorized — key expired |
Scope check (require_scope()) |
403 Forbidden — insufficient scope |
Rate limit check (rate_limit_rpm) |
429 Too Many Requests |
last_used_at update |
Logged; does not block request |
Expiry
- API keys may optionally have an
expires_attimestamp. - If set, the key is rejected after the expiry time.
- Expired keys remain in the database but are non-functional.
Revocation
- Users can delete their API keys through the dashboard or API.
- Deletion removes the key record from the database.
- Deleted keys are immediately non-functional (next request will fail prefix lookup).
Rotation (Current Limitation)
There is currently no automated key rotation workflow. To rotate a key:
- Create a new API key with the desired scopes.
- Update the integration to use the new key.
- Delete the old key.
This is a manual process. Automated rotation is on the roadmap. See Planned Improvements.
Session Management
JWT Session Lifecycle
| Phase | Details |
|---|---|
| Creation | User authenticates via Supabase Auth; JWT issued |
| Duration | Determined by Supabase Auth configuration |
| Refresh | Supabase client SDK handles token refresh automatically |
| Expiry | JWT exp claim enforced on every request |
| Revocation | ⚠️ Not currently supported — JWTs are valid until expiry |
Session Security
| Control | Status |
|---|---|
| Token expiry enforcement | ✅ Implemented |
| Audience validation | ✅ Implemented (authenticated) |
| Signature verification | ✅ Implemented (HS256) |
| Session revocation | ❌ Not implemented |
| MFA enforcement | ❌ Not implemented |
| Concurrent session limits | ❌ Not implemented (WebSocket streams are limited) |
Known Limitations
- No session revocation: Once a JWT is issued, it cannot be revoked before its natural expiry. If a user's account is compromised, the attacker's token remains valid until it expires.
- No MFA: Multi-factor authentication is not currently enforced. Supabase Auth supports MFA, but it is not required by the application.
- No session listing: Users cannot view or manage their active sessions.
Admin Access Controls
Who Can Be Admin
Admin status is controlled by the ADMIN_USER_IDS environment variable, which contains a comma-separated list of Supabase user IDs. This is set at deployment time and requires a redeployment to change.
| Property | Value |
|---|---|
| Assignment | Environment variable (ADMIN_USER_IDS) |
| Format | Comma-separated Supabase user UUIDs |
| Modification | Requires environment variable update + redeployment |
| Verification | verify_admin dependency checks user ID against list |
What Admins Can Do
Admin endpoints (under /admin) provide:
| Capability | Endpoint Pattern | Details |
|---|---|---|
| View all users | GET /admin/users |
List all platform users |
| View any conversation | GET /admin/conversations |
Access conversations across all users |
| Platform analytics | GET /admin/analytics |
Usage statistics, platform metrics |
| System configuration | Various /admin/* |
Platform-level settings |
| User management | Various /admin/users/* |
View user details and activity |
Admin Audit Gap
⚠️ Known Gap: Admin route access is not currently covered by the audit logging middleware. Admin actions are not recorded in the audit log. This is a planned improvement.
WebSocket & Streaming Access Controls
Authentication
WebSocket and streaming endpoints require the same authentication as REST endpoints:
- JWT Bearer token in the initial HTTP upgrade request, or
- API key with
streaming:writescope.
Authorization
| Control | Details |
|---|---|
| Authentication | Required for WebSocket upgrade |
| Ownership | Users can only access their own streams |
| Concurrent Limits | Per-user maximum concurrent WebSocket streams |
| Scope | streaming:read / streaming:write for API key access |
Rate Limiting
- WebSocket connections are subject to per-user concurrent connection limits.
- This prevents resource exhaustion from a single user opening excessive streams.
Third-Party Access
OAuth Clients
Third-party applications can integrate with Perceive8 via OAuth Client Credentials:
| Property | Details |
|---|---|
| Registration | Via API; returns client_id and client_secret |
| Secret Storage | bcrypt-hashed |
| Token Issuance | Client credentials exchange for JWT |
| Scope | Defined per client at registration |
| Status | Alpha |
Webhook Delivery
Perceive8 delivers webhook events to customer-configured endpoints:
| Security Control | Details |
|---|---|
| HMAC-SHA256 Signing | Every payload is signed; recipients should verify |
| Domain Verification | DNS TXT record verification before delivery |
| TLS Enforcement | Webhooks delivered only over HTTPS |
| Retry Policy | Failed deliveries are retried with backoff |
See Webhooks Documentation for integration details.
Access Control Matrix
By Authentication Method
| Endpoint Category | JWT (Dashboard) | API Key | OAuth Client | Query Param Token |
|---|---|---|---|---|
| Conversations CRUD | ✅ | ✅ (scoped) | ✅ (scoped) | ❌ |
| Audio Upload | ✅ | ✅ (scoped) | ✅ (scoped) | ❌ |
| Analysis Results | ✅ | ✅ (scoped) | ✅ (scoped) | ❌ |
| Streaming (WebSocket) | ✅ | ✅ (scoped) | ❌ | ❌ |
| SSE/EventSource | ✅ | ❌ | ❌ | ✅ |
| API Key Management | ✅ | ❌ | ❌ | ❌ |
| Webhook Management | ✅ | ✅ (scoped) | ❌ | ❌ |
| OAuth Client Management | ✅ | ❌ | ❌ | ❌ |
| Billing / Subscription | ✅ | ✅ (scoped) | ❌ | ❌ |
| Scenarios | ✅ | ✅ (scoped) | ✅ (scoped) | ❌ |
| Reports | ✅ | ✅ (scoped) | ✅ (scoped) | ❌ |
| Speakers / Voiceprints | ✅ | ✅ (scoped) | ❌ | ❌ |
| Alerts | ✅ | ✅ (scoped) | ❌ | ❌ |
| Admin Endpoints | ✅ (admin only) | ❌ | ❌ | ❌ |
| Health Check | Public | Public | Public | Public |
By Role
| Capability | Regular User | Admin |
|---|---|---|
| Own resource CRUD | ✅ | ✅ |
| Cross-user resource access | ❌ | ✅ |
| API key management (own) | ✅ | ✅ |
| Webhook management (own) | ✅ | ✅ |
| Platform analytics | ❌ | ✅ |
| User management | ❌ | ✅ |
| System configuration | ❌ | ✅ |
Principle of Least Privilege
Perceive8 implements least privilege through several mechanisms:
1. Default Deny
- All endpoints require authentication by default.
- Only explicitly public endpoints (health check) are accessible without credentials.
- API keys without the required scope are denied access.
2. Scoped API Keys
- API keys are created with specific scopes.
- A key created for webhook management cannot access conversation data.
- Customers are encouraged to create purpose-specific keys with minimal scopes.
3. Resource Isolation
- Users can only access their own resources.
- No cross-user data access is possible through regular user endpoints.
- Database-level RLS provides a second enforcement layer.
4. Admin Restriction
- Admin capabilities are restricted to an explicit allowlist.
- Admin status cannot be self-assigned.
- Admin endpoints are separated from regular user endpoints.
5. Time-Limited Access
- JWT tokens expire and must be refreshed.
- API keys can be created with expiry dates.
- Temporary access can be granted via short-lived API keys.
Known Limitations & Planned Improvements
Current Limitations
| Limitation | Risk | Current Mitigation | Planned Resolution |
|---|---|---|---|
| No MFA enforcement | Medium | Strong password requirements via Supabase Auth | Enable MFA support and enforcement (Q2 2026) |
| No session revocation | Medium | JWT expiry limits exposure window | Implement token blocklist or short-lived tokens (Q3 2026) |
| No API key rotation workflow | Medium | Manual rotation possible (create new, delete old) | Automated rotation with overlap period (Q2 2026) |
| Admin routes not audited | Medium | Admin access restricted to allowlist | Extend audit middleware to admin routes (Q2 2026) |
| No distributed rate limiting | Low | In-memory rate limiting functional for single-instance | Implement Redis-based distributed rate limiting (Q3 2026) |
| Scopes immutable after creation | Low | Create new key with desired scopes | Implement scope modification API (Q3 2026) |
| No session listing/management | Low | JWT expiry limits session lifetime | Implement session management UI (Q4 2026) |
| OAuth Client Credentials in Alpha | Low | Functional but API may change | Stabilize and release GA (Q3 2026) |
| Admin assignment via env var only | Low | Requires deployment access to modify | Implement admin management UI (Q4 2026) |
Improvement Roadmap
Q2 2026
- MFA support and optional enforcement
- API key rotation workflow with overlap period
- Audit logging for admin routes
- API key usage analytics dashboard
Q3 2026
- Session revocation mechanism (token blocklist)
- Distributed rate limiting via Redis
- OAuth Client Credentials GA release
- Scope modification for existing API keys
- IP allowlisting for API keys
Q4 2026
- Session management UI (view/revoke active sessions)
- Admin management UI (replace env var approach)
- Fine-grained RBAC (custom roles beyond admin/user)
- API key usage alerts and anomaly detection
Related Documents
| Document | Description |
|---|---|
| Security Overview | Customer-facing security summary |
| Encryption | Encryption standards and key management |
| Incident Response Plan | Security incident procedures |
| Data Inventory | Data flow documentation |
| Consent Model | User consent and data processing basis |
| GDPR Readiness | GDPR compliance assessment (coming soon) |
| DPA Template | Data Processing Agreement (contact support) |
Document History
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0 | 2026-03-07 | Perceive8 Engineering | Initial release |
This document is reviewed quarterly. Next scheduled review: 2026-06-07.