Authentication and 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 5 middlewares:
AuthAnonymousCtx
— which sets the anonymous sender context by defaultAuthCookiesCtx
— which handlesauthentication
through theCookie
header.AuthAuthorizationCtx
— which handlesauthentication
through theAuthorization
header.AuthTargetCtx
- which augments request with target information.AuthCommandHandlerFunc
(andAuthQueryHandlerFunc
) — which validates user permissions and enforces access control within theFacade
.
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.
Flow Overview
The user sends an HTTP request to the REST API, which triggers the following flow:
- Authentication:
- The request is first processed by the
api
middleware, which includes theAuthAnonymousCtx
,AuthCookiesCtx
,AuthAuthorizationCtx
, andAuthTargetCtx
middleware. - The
AuthAnonymousCtx
middleware sets the default anonymous sender context. - The
AuthCookiesCtx
middleware handles authentication through theCookie
header. - The
AuthAuthorizationCtx
middleware handles authentication through theAuthorization
header. - The
AuthTargetCtx
middleware augments the request with target information (which can be adapted to user's requirements with differentaggregateUuid
field name)
- The request is first processed by the
- Authorization:
- The request is then dispatched to the
Facade
, where theAuth
middleware enforces access control based on the user's permissions.
- The request is then dispatched to the
Stage 1: Authentication
Anonymous Context
The AuthAnonymousCtx
middleware sets the default anonymous sender context in the request context. This context is used when no authentication credentials are provided in the request. The middleware begins by setting default values in the context to represent an anonymous user:
sessionUuid
: Default toANONYMOUS_SESSION_UUID
accountUuid
: Default toANONYMOUS_ACCOUNT_UUID
identityUuid
: Default toANONYMOUS_IDENTITY_UUID
tenantUuid
: Default toANONYMOUS_TENANT_UUID
Cookies Context
The middleware retrieves the Cookie
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 Cookie
header:
- Cookie Format: The Cookie Header is defined following:
Cookie: session=<sessionUuid|sessionKey>
- HttpOnly and Secure (User Login)Cookie: session=<sessionUuid|sessionKey>, identity=<tenantUuid|identityUuid>
- Secure (Default Usage after Login and Identity Selection)
The middleware sets the sessionUuid
and accountUuid
in the request context if a valid session is found. If an identityUuid
is also present in the Cookie header and belongs to the account, the request context is further updated with the identityUuid
and tenantUuid
.
Authorization Header
The AuthAuthorizationCtx
middleware handles the extraction and validation of authentication credentials from the HTTP request's Authorization
header. The Authorization Header is defined following:
- Authorization Format:
Authorization: Bearer sa=<tokenUuid|tokenKey>
- Service Account connected with specific identity
The middleware sets the sessionUuid
and accountUuid
in the request context if a valid session is found. 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
. In addition, the middleware also handles service account token authentication, which is the primary use case for this middleware.
Note: User Account authentication should be handled by the AuthCookiesCtx
middleware (using Cookies
) and Service Account authentication by the AuthAuthorizationCtx
middleware (using Authorization
).
Final Context
After the AuthAnonymousCtx
, AuthCookiesCtx
, and AuthAuthorizationCtx
middlewares have processed the request, the request context is updated with the following fields:
sessionUuid
: The session UUID extracted from the request'sCookie
orAuthorization
header.accountUuid
: The account UUID extracted from the request'sCookie
orAuthorization
header.identityUuid
: The identity UUID extracted from the request'sCookie
orAuthorization
header.tenantUuid
: The tenant UUID extracted from the request'sCookie
orAuthorization
header. Next, the request proceeds to theAuthTargetCtx
middleware for context augmentation.
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.
- 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 defaulttenantUuid
field name identifier.AuthTargetCtxWithAggregateFieldName
- Overwrites the defaultaggregateUuid
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.
- 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.
- 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.
- Request Context Extraction:
- The request context (
reqCtx
) is extracted from the command usingcmd.GetReqCtx()
. This context contains crucial information like the sender's identity, tenant, and target aggregates for permission validation.
- System Layer Authorization:
- Checks whether authorization should be skipped by evaluating
reqCtx.Attributes.Get(auth.ExecuteSkipAuthorization)
. If this is true, the command is allowed to proceed.
- 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
orQryFunc
). If the custom function approves, the command (or query) is allowed.
- It searches through a list of custom permissions (
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.
- The sender's identity context is fetched. The middleware checks if the sender belongs to a privileged group (like
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.
Security Considerations
The comby framework incorporates robust security measures to safeguard user data and ensure secure account management:
Regardless of the encryption used for the storage layer, all passwords, keys, and tokens are further encrypted/hashed with bcrypt. This ensures that even if an attacker gains access to the underlying unencrypted storage, sensitive information such as passwords, session keys, and tokens remains unreadable. This design choice necessitates that users provide these credentials, along with an associated UUID, to locate the relevant database entries for cryptographic comparison.
Example: When a user logs in, they must provide their email, and password. The password and session key are encrypted with bcrypt. The provided password is cryptographically compared to the stored value, and the encrypted session key is securely saved in the database. As a result, the application cannot retrieve a user’s password or session key in plaintext, as these credentials are securely encrypted and protected against unauthorized access.
Using Cookies in frontend applications offers distinct security advantages, particularly for mitigating attacks such as Cross-Site Request Forgery (CSRF). Here's a more detailed explanation:
Cookies serve as a secure mechanism for managing user sessions in frontend applications, providing inherent protection against CSRF attacks. When a user successfully logs in, the server generates a new session token and returns it to the client as part of the HTTP response, typically in a Set-Cookie header.
The browser automatically handles storing this cookie securely, adhering to attributes such as HttpOnly, Secure, and SameSite to bolster security:
HttpOnly: This attribute prevents JavaScript on the frontend from accessing the cookie, safeguarding it against attacks such as Cross-Site Scripting (XSS). This ensures that even if malicious scripts are injected into the page, they cannot access or steal the session token stored in the cookie.
Secure: Cookies marked with this attribute are transmitted only over secure HTTPS connections. This prevents interception of sensitive data during transmission, further reducing the risk of session hijacking through man-in-the-middle attacks.
SameSite: By setting this attribute to
Strict
orLax
, the browser restricts the inclusion of cookies in cross-site requests, mitigating CSRF attacks by ensuring that cookies are sent only with same-origin requests.
Unlike Authorization headers
, which require explicit handling and storage (e.g., in localStorage
or sessionStorage
), cookies leverage the browser’s built-in mechanisms for secure storage and transmission. This eliminates the need for direct interaction with cookies in frontend code, reducing the surface area for potential vulnerabilities.