05

Enterprise Identity and Tenant Isolation

Give Workboard enterprise sign-in without confusing identity, delegated API access, application permissions, tenant selection, or the data boundary—and prove that two tenants with matching local identifiers remain isolated.

Separate authentication from authorization

Authentication establishes which principal is interacting with Workboard; a principal is a stable identifier for a person or workload. Authorization decides whether that authenticated principal may perform a particular action on a particular resource in a particular tenant. A valid login is evidence for the first decision, never an automatic “yes” for the second.

Keep four questions separate at their enforcement seams:

QuestionMechanismWorkboard answer
Who signed in?OpenID Connect authenticationPrincipal issuer + subject
May Workboard call another API for them?OAuth delegated authorizationAccess token with audience and scopes
May they update this board?Application authorizationActive membership plus role/resource/action policy
Which tenant’s rows may this operation touch?Tenant isolationVerified tenant context plus independent data predicate

Single sign-on (SSO) lets an enterprise identity provider authenticate a person for multiple applications. SSO improves account lifecycle and login assurance, but it does not encode Workboard’s owner, editor, or viewer rules. Likewise, an OAuth scope such as workboard.api says what an access token may be presented to; it does not prove that its bearer belongs to Tenant Alpha or may edit board 42.

Model the final decision explicitly:

{
  "subjectId": "https://idp.example|00u123",
  "tenantId": "alpha",
  "resource": "board:42",
  "action": "todo.create",
  "effect": "allow",
  "policyVersion": "workboard-authz-12",
  "expiresAt": "2026-07-26T12:05:00Z"
}

Every request starts unauthenticated and unauthorized. Missing identity, tenant, membership, action, resource, or policy version fails closed; no default tenant and no inherited “admin-like” behavior fill the gap.

Use OIDC for identity and OAuth for delegated access

OpenID Connect (OIDC) is an identity layer over OAuth 2.0 that lets Workboard verify an authentication event and receive identity claims. OAuth 2.0 is a framework for delegated access: it lets a client obtain an access token for a resource server without learning the user’s password.

An OIDC ID token is for Workboard, the relying party, to establish the signed-in identity. An OAuth access token is for a specific API, called the resource server. Do not send ID tokens to APIs as access credentials, and do not interpret a generic access token as proof that a user authenticated for this Workboard session.

At the callback, validate the ID token using the provider’s configured metadata and keys. Check its cryptographic signature, exact issuer, intended audience and authorized party where applicable, expiry and other time claims, and the nonce bound to the login attempt. Validate the authorization response’s state value to bind it to the browser flow and defend against cross-site request forgery. Use the stable pair of issuer and subject as the external identity key; email addresses are display or contact attributes and may change or be reassigned.

OAuth delegation remains separate. If Workboard later calls a calendar API to add todo deadlines, request only the necessary audience and scopes, store the resulting tokens server-side, and perform a fresh application-authorization decision before using them. Possession of a calendar token neither selects a Workboard tenant nor grants board access.

The diagram has two destinations on purpose: authentication feeds the Workboard session and policy, while delegated OAuth credentials feed only their intended external API.

Run enterprise SSO with Authorization Code and PKCE

The Authorization Code flow returns a short-lived, one-time code to Workboard, which exchanges it at the token endpoint. Proof Key for Code Exchange (PKCE) binds that code to the client instance that started the flow, reducing the value of an intercepted code.

Before redirecting, Workboard creates a high-entropy code verifier and sends its derived S256 code challenge, along with state and an OIDC nonce. The provider authenticates the user. The callback must exactly match a pre-registered redirect URI; Workboard checks state, exchanges the code with the original verifier, validates the returned ID token and nonce, and only then creates a session.

PKCE does not replace state, nonce, exact redirect matching, client authentication for confidential clients, or token validation. Each protects a different seam. Do not accept tokens from a provider selected only by an untrusted request parameter; bind an organization’s configured issuer to trusted tenant configuration and protect that configuration through an administrative workflow.

If login succeeds but the principal has no active Workboard membership, authentication still succeeded. Show a neutral no-access state or an invitation flow; do not silently create a tenant, select the first organization claim, or grant a role from an unverified domain suffix.

Manage sessions and token lifetimes

A session is Workboard’s server-controlled record that links a browser to an authenticated principal and current security state. Tokens are protocol credentials with their own audiences and lifetimes; keeping them distinct lets Workboard terminate a session without pretending every issued token disappeared.

Prefer an opaque, high-entropy session identifier in a Secure, HttpOnly, appropriately SameSite cookie. Store identity, memberships, token material, timestamps, and risk state on the server or behind an equivalently defended backend-for-frontend. Never place refresh tokens in browser-readable storage. Protect state-changing requests against cross-site request forgery even when cookie attributes reduce exposure.

Use both idle and absolute session limits. Rotate the session identifier after authentication, privilege elevation, membership or tenant switch, password or factor recovery, and other security-sensitive changes. Recheck current membership and policy for important actions instead of freezing authorization for the session’s entire lifetime.

Access tokens should be short-lived and audience-restricted. Refresh tokens are more powerful because they mint new access tokens: rotate them on use where supported, detect reuse, bind them to the client where practical, and revoke their authorization grant when access ends. Token logs must contain identifiers or hashes, not bearer secrets.

Self-contained tokens can be validated without contacting their issuer, which improves availability but delays knowledge of revocation until expiry unless an online status or denylist design is added. Opaque tokens can support introspection but introduce an online dependency and caching choices. Choose deliberately and document the maximum stale-authorization window.

Derive tenant context from verified identity

A tenant context is the server-established organization boundary within which a request is authorized and executed. Workboard may receive tenantId=alpha in a URL, header, request body, job payload, or model-generated tool argument, but every one of those values is only a requested selector—not proof of access.

Start from the validated OIDC identity key. Load active Workboard memberships for that principal from an authoritative store, then intersect them with the requested tenant. Enforce one membership record per (tenantId, principalId) so duplicate active rows cannot produce conflicting roles. Exactly one active, permitted membership produces the tenant context. A tenant claim from an identity provider may help map enterprise accounts, but accept it only under a configured issuer-to-organization mapping and still require current application membership.

Carry the resulting context in typed server-side request state. For background jobs, persist an immutable tenant identifier plus initiating principal and policy version, then reauthorize before execution if the job can outlive the original decision. For subscriptions, bind the context during the authenticated handshake and recheck it on reconnect and policy changes.

Never trust the coding model to infer the tenant from names, email domains, recent UI state, or resource identifiers. Tenant selection is an authorization input derived by trusted code, not a natural-language judgment.

Enforce authorization at API and data boundaries

Tenant isolation needs two independent enforcement layers so that one coding mistake is not enough to expose another organization. The API layer decides the action and loads resources by a compound key; the data layer independently restricts rows using a tenant predicate or row-level security policy.

At the API boundary, construct an AuthorizationDecision from the verified principal, tenant context, resource, action, active membership, role, and policy version. Then load a resource using both identifiers. Never load todo WHERE id = ? and compare tenants afterward, because pre-authorization logging, errors, joins, or caches may already have leaked information.

SELECT id, board_id, title, state
FROM todos
WHERE tenant_id = :tenant_id
  AND id = :todo_id;

Apply the same pattern to get, list, search, export, create, update, delete, attachment/blob retrieval, and subscription authorization. For writes, tenant identifiers come from trusted context, not the submitted body. Enforce tenant-scoped foreign keys and uniqueness so a Beta todo cannot point at an Alpha board even if application code is wrong.

The data layer must apply its own predicate. Database row-level security (RLS) is a database feature that filters or rejects rows according to policy; where RLS is unavailable, use a constrained data-access interface that requires tenant context and test it independently. Run Workboard under the same restricted database role used in production and prove that role cannot bypass the policy. For example, PostgreSQL superusers, roles with BYPASSRLS, and normally the table owner can bypass RLS, so role design and FORCE ROW LEVEL SECURITY where applicable are part of the control. The API decision and data predicate should fail separately under mutation tests.

Tenant-scope every indirect key: cache keys, object-storage prefixes and signed blob URLs, search indexes or mandatory filters, queue topics, idempotency keys, rate-limit counters, analytics partitions, and background-job leases. A globally keyed cache can leak a correct Alpha response into Beta even when the database query is perfect.

RoleBoard readTodo editMembers manageTenant settings
ViewerAllowDenyDenyDeny
EditorAllowAllowDenyDeny
OwnerAllowAllowAllowAllow

Roles are shortcuts for policy inputs, not a replacement for resource and tenant checks. An Alpha owner has no permission in Beta without a separate active Beta membership.

Falsify the tenant boundary

A tenant-isolation falsifier deliberately creates confusing data and attempts to cross the boundary. The strongest Workboard fixture gives Tenant Alpha and Tenant Beta the same local resource identifier, so code that scopes only by id selects a plausible but wrong row.

Create board:42, todo:7, and attachment:9 in both tenants with distinguishable secret marker values. Authenticate as an Alpha editor. Request Beta’s versions through every path while supplying Alpha context, then reverse the test with a Beta user. Expected denial responses must not reveal whether the foreign target exists; timing, status, body shape, logs visible to the caller, and subscription behavior should be equivalent to an inaccessible or absent resource.

Cover direct reads, lists, searches, exports, creates with foreign parent IDs, updates, deletes, attachment redirects and signed URLs, warmed cache hits, queued jobs, and websocket subscriptions. Prime the cache with Beta’s value before Alpha requests the same local ID. Start an authorized subscription, revoke membership, and verify reauthorization closes or denies it within the documented control window.

for (const path of tenantSensitivePaths) {
  const result = await path.attempt({
    authenticatedTenant: "alpha",
    requestedTenant: "beta",
    localResourceId: "42"
  });
  expect(result).toMatchUniformDenial();
  expect(result.body).not.toContain("BETA-SECRET-MARKER");
}

Run the falsifier against the API policy with the data predicate intact, then against the data policy with an intentionally weakened API in an isolated mutation environment. Both layers must independently prevent leakage. Record the paths tested and the evidence in the Workboard ledger; “one endpoint denied” is not evidence for the whole boundary.

Further reading

These primary specifications and official security guides define the protocols and validation boundaries used here. Status and dates distinguish stable standards from living guidance; all links were accessed on 2026-07-26.

Key takeaways

The central design rule is to establish identity once, authorize every action, and enforce tenant isolation independently at the API and data layers. No token, URL field, role name, or model output may substitute for verified identity plus current membership.

  • OIDC authenticates the principal; OAuth delegates API access; Workboard policy authorizes application actions.
  • Authorization Code with PKCE, state, nonce, exact redirect matching, and token validation protects distinct parts of SSO.
  • Server-controlled sessions and token lifetimes make credential exposure and revocation windows explicit.
  • Tenant context comes from verified issuer/subject identity intersected with active membership, never a request selector alone.
  • Every direct and indirect access path needs a compound tenant/resource key and an independent data predicate.
  • Tenant-scoped caches, blobs, indexes, queues, jobs, and subscriptions are part of the isolation boundary.
  • Alpha/Beta fixtures with the same local IDs expose missing tenant predicates that ordinary tests miss.

Checklist

Use this checklist before Workboard’s enterprise identity and tenant boundary can be treated as release evidence. Each checked item should correspond to configuration, a policy test, or a denial artifact.

  • [ ] Authentication, OAuth delegation, application authorization, and tenant isolation have separate documented decisions.
  • [ ] OIDC validation covers signature, issuer, audience, time, nonce, and state at the correct seams.
  • [ ] Authorization Code uses PKCE with S256 and an exact registered redirect URI.
  • [ ] Browser sessions use secure server-controlled state and rotate after authentication or privilege change.
  • [ ] Refresh and access-token lifetimes, rotation, storage, and stale-authorization windows are documented.
  • [ ] Tenant context derives from verified identity plus current active membership.
  • [ ] API loads and writes use compound tenant/resource keys for every path.
  • [ ] A separate data predicate or RLS policy fails closed when API code is weakened.
  • [ ] Cache, search, blob, queue, job, export, and subscription keys include tenant context.
  • [ ] Alpha/Beta same-local-ID tests prove uniform denial without existence or content leakage.