Skip to content

Authorization

In the comby framework, the Facade itself does not handle any authorization logic. Instead, authorization is managed by comby's defaults, which users can register to.

TIP

Further information about Defaults can be found in the documentation: Defaults

The defaults ensures proper authorization through 3 middlewares:

  • AuthSenderCtx — which handles authentication
  • AuthTargetCtx - which augments request with target information.
  • AuthCommandHandlerFunc (and AuthQueryHandlerFunc) — which validates user permissions and enforces access control within the Facade.

The Facade simply executes Commands and Queries, relying on these middleware layers to enforce security.

When a user makes an HTTP request, the request is processed in multiple stages by the above middleware components. These components work together to ensure that the request is authenticated and finally authorized based on the user's permissions.

Authorization Flow Overview

The user sends an HTTP request to the REST API, which triggers the following flow:

  • The API receives the request and first processes it through api/middleware/sender.ctx.go, responsible for authentication.
  • Afterward, it augments the request with target data through api/middleware/target.ctx.go which can be adapted to user's requirements (different aggregateUuid field name).
  • The facade uses default's domain/auth/middleware.go to determine whether the request is authorized to proceed based on user permissions.

Stage 1: Authentication with sender.ctx.go

The AuthSenderCtx middleware handles the extraction and validation of authentication credentials from the HTTP request's Authorization header.

Authorization Header Format

The Authorization Header is defined following:

  • Authorization: Bearer session=<sessionKey> - eg. User Login
  • Authorization: Bearer session=<sessionKey>, identity=<identityUuid> - default usage
  • Authorization: Bearer sa=<token> - for service accounts only (no user account connected)

Authentication Flow

  1. Set Default Anonymous Values:
  • The middleware begins by setting default values in the context to represent an anonymous user:
  • sessionUuid: Default to ANONYMOUS_SESSION_UUID
  • accountUuid: Default to ANONYMOUS_ACCOUNT_UUID
  • identityUuid: Default to ANONYMOUS_IDENTITY_UUID
  • tenantUuid: Default to ANONYMOUS_TENANT_UUID
  1. Extract Authorization Header:
  • The middleware retrieves the Authorization header from the request. If the header is present and the AccountCtxReadmodel (rm) is provided, it attempts to authenticate the user. These fields are extracted from the header:
    • sessionKey: The session key used for user authentication.
    • identityUuid: The identityUuid used for user authentication.
    • serviceAccountToken: The service account token used for service account authentication.
  1. Session Key Authentication:
  • If a sessionKey is found in the Authorization header, the middleware fetches the session using rm.GetSessionByKey(sessionKey). If a valid session is found, the request context is updated with the sessionUuid and the associated accountUuid - overwriting the anonymous ones. Rejects the session if it has expired.
  • If an identityUuid is also present in the Authorization header and belongs to the account, the request context is further updated with the identityUuid and tenantUuid.
  1. Service Account Token Authentication:
  • If a serviceAccountToken token is found in the Authorization header, the middleware fetches the service account's identity and tenant using rm.GetServiceAccountToken(serviceAccountToken). If valid, the request context is updated accordingly with identityUuid and tenantUuid. Rejects the token if it has expired.
  1. Proceed to the Next Middleware:
  • After processing the Authorization header and updating the request context (reqCtx), the middleware invokes the next handler in the chain with the updated context.

Stage 2: Context Augmentation with target.ctx.go

The AuthTargetCtx middleware is responsible for extracting and adding target-specific information, such as tenantUuid and aggregateUuid, from the request to the context. This information is used later in the authorization phase.

Normally targetTenantUuid is the same as the senderTenantUuid, but there are cases where a privileged user needs this distinction, for example to create new tenants. Here the acting tenant can be A but the destinated new tenant B.

The aggregatUuid has a different purpose: it is used to define which target aggregate should be finally processed. The next middleware checks whether the target aggregate exists within the target tenant. and not an aggregate from another tenant. This always ensures that even manipulated requests (eg requesting with tenant A, but deleting aggregate B which lives in tenant C) are checked for accuracy.

  1. Field Name Customization:

By default, the values tenantUuid and aggregateUuid are extracted using the field names (tenantUuid and aggregateUuid). These field names can be customized using options such as:

  • AuthTargetCtxWithTenantFieldName - Overwrites the default tenantUuid field name identifier.
  • AuthTargetCtxWithAggregateFieldName - Overwrites the default aggregateUuid field name identifier.

When a user defines a URL endpoint, such as: /myTenants/{myTenantUuid}/customAggregates/{customAggregateUuid} the user can explicitly specify tenant and aggregate field names using:

  • AuthTargetCtxWithTenantFieldName("myTenantUuid")
  • AuthTargetCtxWithAggregateFieldName("customAggregateUuid")

This precise definition is crucial for proper authorization of the request, ensuring that cross-tenant requests are reliably blocked and that only aggregates from the user's own tenant are accessed. Alternatively user can also use RequestContext directly to set the target tenantUuid and aggregateUuid values manually.

  1. Extracting Tenant and Aggregate UUIDs:

The tenantUuid and aggregateUuid are extracted from the request's path based on the field names and added to the request context if valid.

  1. Proceed to Next Middleware:

After augmenting the context with the target-specific data, the request proceeds to the next middleware or handler.

Stage 3: Authorization with auth/middleware.go

The final stage of the authorization process is handled by the middleware defined in domain/auth/middleware.go. This middleware evaluates whether the user has the necessary permissions to execute a command or query based on various authorization checks, including custom permission functions and RBAC.

AuthCommandHandlerFunc wraps a command handler with authorization logic. Same for AuthQueryHandlerFunc for queries. The middleware ensures that commands (or queries) are only executed if the user has the required permissions based on system-level checks, RBAC (role-based access control), and custom permission functions.

  1. Request Context Extraction:
  • The request context (reqCtx) is extracted from the command using cmd.GetReqCtx(). This context contains crucial information like the sender's identity, tenant, and target aggregates for permission validation.
  1. System Layer Authorization:
  • Checks whether authorization should be skipped by evaluating reqCtx.ExecuteSkipAuthorization. If this is true, the command is allowed to proceed.
  1. RBAC Layer Authorization:
  • If system layer authorization doesn't allow the command, the RBAC layer is evaluated. The process involves two major checks:

  • Custom Permission Evaluation:

    • It searches through a list of custom permissions (runtime.RuntimePermissionList(fc)). If the command’s domain and type match a permission, it runs the custom function (CmdFunc or QryFunc). If the custom function approves, the command (or query) is allowed.
  • Identity and Group Permission Evaluation:

    • The sender's identity context is fetched. The middleware checks if the sender belongs to a privileged group (like SYSTEM_TENANT_GROUP_ADMIN_UUID), granting full access.
    • For other identities, RBAC ensures the identity has the necessary permissions within the target tenant and aggregate.
    • If cross-tenant requests are made (excluding system-tenant), or if the target aggregate does not belong to the tenant, the request is denied.
  • If the authorization passes, the original command handler (or query handler) is executed. Otherwise, an error ErrPermissionDenied is returned, indicating the command (or query) was denied by the authorization layer.