# 3.Cơ chế xác thực (JWT/Bearer)

## 3.1. Mục Tiêu Chương

Chương này quy định chuẩn xác thực và nguyên tắc thiết kế bảo mật nhằm:

- Đảm bảo cơ chế xác thực thống nhất giữa các integration flow và đơn vị phát triển
- Giúp artifact dễ đọc, dễ hiểu và dễ bảo trì
- Giảm lỗi phát sinh do xử lý token không đúng chuẩn
- Tăng hiệu quả review và kiểm soát chất lượng artifact
- Đảm bảo toàn bộ luồng từ Client → APIM → MI → Backend đều có xác thực nhất quán
- Tích hợp đúng với WSO2 Identity Server (IS) làm trung tâm cấp và xác thực token

## 3.2. Khái Niệm / Phạm Vi Áp Dụng

Quy định này áp dụng cho:

- Toàn bộ artifact xử lý hoặc chuyển tiếp request qua xác thực
- **Core Team** và **Partner Team**
- Tất cả các API, Sequence, Inbound Endpoint trong `artifacts/**`
- Cấu hình APIM và IS do Core Team quản lý

### Kiến Trúc Xác Thực Tổng Thể

[![image.png](https://docs.lifetex.vn/uploads/images/gallery/2026-02/scaled-1680-/Oz9image.png)](https://docs.lifetex.vn/uploads/images/gallery/2026-02/Oz9image.png)

### Ba Tầng Xác Thực

<table border="1" id="bkmrk-t%E1%BA%A7ng-th%C3%A0nh-ph%E1%BA%A7n-vai-" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 33.3694%;"></col><col style="width: 33.3694%;"></col><col style="width: 33.3694%;"></col></colgroup><tbody><tr><th class="bg-gray-500/20 px-6 py-2 text-left font-medium">Tầng</th><th class="bg-gray-500/20 px-6 py-2 text-left font-medium">Thành phần</th><th class="bg-gray-500/20 px-6 py-2 text-left font-medium">Vai trò xác thực</th></tr><tr><td class="px-6 py-2">**Cấp token**</td><td class="px-6 py-2">WSO2 Identity Server (IS)</td><td class="px-6 py-2">OAuth2 Authorization Server, cấp JWT/Access Token</td></tr><tr><td class="px-6 py-2">**Validate token**</td><td class="px-6 py-2">WSO2 API Manager (APIM)</td><td class="px-6 py-2">Xác thực token tại Gateway trước khi route vào MI</td></tr><tr><td class="px-6 py-2">**Forward token**</td><td class="px-6 py-2">WSO2 Micro Integrator (MI)</td><td class="px-6 py-2">Set `Authorization: Bearer` header khi gọi backend</td></tr></tbody></table>

## 3.3. Quy Định Chính

### 3.3.1. Quy Tắc Đặt Tên Artifact

Các artifact liên quan xác thực phải đặt tên theo quy ước thống nhất:

<table border="1" id="bkmrk-th%C3%A0nh-ph%E1%BA%A7n-quy-t%E1%BA%AFc-%C4%91" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 33.3694%;"></col><col style="width: 33.3694%;"></col><col style="width: 33.3694%;"></col></colgroup><tbody><tr><th class="bg-gray-500/20 px-6 py-2 text-left font-medium">Thành phần</th><th class="bg-gray-500/20 px-6 py-2 text-left font-medium">Quy tắc đặt tên</th><th class="bg-gray-500/20 px-6 py-2 text-left font-medium">Ví dụ</th></tr><tr><td class="px-6 py-2">REST API</td><td class="px-6 py-2">`<Name>Api.xml`</td><td class="px-6 py-2"><div>  
</div><span class="inline-flex items-center gap-0.5 rounded-md align-middle text-sm font-medium transition-[opacity,background-color] cursor-pointer hover:bg-gray-500/20 select-text translate-y-[-1px]" draggable="true"><span class="inline-flex break-all">KafkaProducerApi.xml</span></span>, <div>  
</div><span class="inline-flex items-center gap-0.5 rounded-md align-middle text-sm font-medium transition-[opacity,background-color] cursor-pointer hover:bg-gray-500/20 select-text translate-y-[-1px]" draggable="true"><span class="inline-flex break-all">PlanningDirectApi.xml</span></span></td></tr><tr><td class="px-6 py-2">Inbound Sequence</td><td class="px-6 py-2">`<flow>-inboundSequence.xml`</td><td class="px-6 py-2"><div>  
</div><span class="inline-flex items-center gap-0.5 rounded-md align-middle text-sm font-medium transition-[opacity,background-color] cursor-pointer hover:bg-gray-500/20 select-text translate-y-[-1px]" draggable="true"><span class="inline-flex break-all">Load\_balance\_example-inboundSequence.xml</span></span></td></tr><tr><td class="px-6 py-2">Error Sequence</td><td class="px-6 py-2">`<flow>-inboundErrorSequence.xml`</td><td class="px-6 py-2"><div>  
</div><span class="inline-flex items-center gap-0.5 rounded-md align-middle text-sm font-medium transition-[opacity,background-color] cursor-pointer hover:bg-gray-500/20 select-text translate-y-[-1px]" draggable="true"><span class="inline-flex break-all">Load\_balance\_example-inboundErrorSequence.xml</span></span></td></tr><tr><td class="px-6 py-2">Inbound Endpoint</td><td class="px-6 py-2">`<flow>.xml`</td><td class="px-6 py-2"><div>  
</div><span class="inline-flex items-center gap-0.5 rounded-md align-middle text-sm font-medium transition-[opacity,background-color] cursor-pointer hover:bg-gray-500/20 select-text translate-y-[-1px]" draggable="true"><span class="inline-flex break-all">Load\_balance\_example.xml</span></span></td></tr><tr><td class="px-6 py-2">Connection Entry</td><td class="px-6 py-2">`<Name>Connection.xml`</td><td class="px-6 py-2"><div>  
</div><span class="inline-flex items-center gap-0.5 rounded-md align-middle text-sm font-medium transition-[opacity,background-color] cursor-pointer hover:bg-gray-500/20 select-text translate-y-[-1px]" draggable="true"><span class="inline-flex break-all">KafkaConnection.xml</span></span></td></tr><tr><td class="px-6 py-2">Backend Endpoint</td><td class="px-6 py-2">`<Name>Endpoint.xml`</td><td class="px-6 py-2">`PlanningBackendEndpoint.xml`</td></tr></tbody></table>

### 3.3.2. Quy Tắc Đặt Property Trong Sequence

Dùng tên property nhất quán khi xử lý token/header:

```xml
<!-- Đúng: Tên property rõ ràng, đúng chuẩn -->
<property name="PAYLOAD"          expression="json-eval($)"    scope="default"/>
<property name="HTTP_STATUS"      expression="$axis2:HTTP_SC"  scope="default"/>
<property name="ERROR_CODE"       expression="get-property('ERROR_CODE')"    scope="default"/>
<property name="ERROR_MESSAGE"    expression="get-property('ERROR_MESSAGE')" scope="default"/>

<!-- Sai: Tên không mô tả ý nghĩa -->
<property name="x"    expression="json-eval($)"   scope="default"/>
<property name="code" expression="$axis2:HTTP_SC" scope="default"/>

```

### 3.3.3. Quy Định Về Logging Xác Thực

Không được bỏ qua bước log khi xử lý request:

```xml
<!-- Sai: Không log trạng thái -->
<call>
    <endpoint>
        <address uri="http://192.168.0.133:8080/api/v1/plannings"/>
    </endpoint>
</call>

```

Phải log đầy đủ các bước:

```xml
<!-- Đúng: Log đầy đủ theo chuẩn dự án -->
<log level="custom">
    <property name="API_NAME" value="PlanningDirectApi"/>
    <property name="STATUS"   value="Nhận request, gọi thẳng Backend..."/>
    <property name="PAYLOAD"  expression="get-property('PAYLOAD')"/>
</log>

```

### 3.3.4. Quy Định Về Token / Header Xác Thực

Token dùng để gọi backend phải được set từ `local-entries` hoặc <span class="inline-flex items-center gap-0.5 rounded-md align-middle text-sm font-medium transition-[opacity,background-color] cursor-pointer hover:bg-gray-500/20 select-text translate-y-[-1px]" draggable="true"><span class="inline-flex break-all">config.properties</span></span> — **không hardcode trong Sequence**:

```xml
<!-- SAI: Hardcode token trong Sequence -->
<header name="Authorization" scope="transport"
    value="Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiJ9..."/>

```

```xml
<!-- ĐÚNG: Lấy token từ property/config -->
<property name="BACKEND_TOKEN"
    expression="get-property('conf:backend.auth.token')"
    scope="default"/>
<header name="Authorization" scope="transport"
    expression="fn:concat('Bearer ', get-property('BACKEND_TOKEN'))"/>

```

## 3.4. Nguyên Lý Tách Biệt Trách Nhiệm (Separation of Concerns)

Tương đương nguyên lý **SOLID** áp dụng trong hệ thống WSO2:

### 3.4.1. S — Mỗi Artifact Một Trách Nhiệm

Mỗi artifact chỉ được thực hiện một nhiệm vụ:

<table border="1" id="bkmrk-artifact-ch%E1%BB%89-%C4%91%C6%B0%E1%BB%A3c-l%C3%A0" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 50.0542%;"></col><col style="width: 50.0542%;"></col></colgroup><tbody><tr><th class="bg-gray-500/20 px-6 py-2 text-left font-medium">Artifact</th><th class="bg-gray-500/20 px-6 py-2 text-left font-medium">Chỉ được làm</th></tr><tr><td class="px-6 py-2">`apis/`</td><td class="px-6 py-2">Nhận HTTP request, set header auth, gọi backend/Kafka, trả response</td></tr><tr><td class="px-6 py-2">`sequences/*-inboundSequence.xml`</td><td class="px-6 py-2">Điều phối luồng message từ Kafka đến backend</td></tr><tr><td class="px-6 py-2">`sequences/*-inboundErrorSequence.xml`</td><td class="px-6 py-2">Ghi nhận lỗi và publish vào `error_topic`</td></tr><tr><td class="px-6 py-2">`inbound-endpoints/`</td><td class="px-6 py-2">Kết nối Kafka, subscribe topic</td></tr><tr><td class="px-6 py-2">`local-entries/`</td><td class="px-6 py-2">Lưu thông tin kết nối và cấu hình dùng chung</td></tr></tbody></table>

**Sai** — Error logic nằm trong inbound sequence:

```xml
<!-- SAI: Inbound sequence tự xử lý lỗi kỹ thuật -->
<sequence name="myFlow-inboundSequence">
    <call>...</call>
    <property name="ERROR_CODE" expression="get-property('ERROR_CODE')"/>
    <kafkaTransport.publishMessages configKey="KafkaConnection">
        <topic>error_topic</topic>
    </kafkaTransport.publishMessages>
    <!-- Đây là nhiệm vụ của inboundErrorSequence -->
</sequence>

```

**Đúng** — Error sequence riêng biệt:

```xml
<inboundEndpoint
    sequence="myFlow-inboundSequence"
    onError="myFlow-inboundErrorSequence">

```

### 3.4.2. O — Mở Rộng Không Sửa Artifact Cũ

Khi thêm topic/luồng mới, tạo artifact mới — không sửa sequence của luồng khác:

```xml
<!-- Thêm luồng mới: tạo file riêng -->
<!-- planning_topic-inboundSequence.xml -->
<!-- planning_topic-inboundErrorSequence.xml -->
<!-- planning_topic.xml (inbound endpoint) -->

```

### 3.4.3. L — Consumer Tuân Thủ Contract Sequence

Inbound Endpoint phải trỏ đúng sequence và error sequence đã khai báo:

```xml
<!-- Sai: onError trỏ sai sequence -->
<inboundEndpoint name="Load_balance_example"
    sequence="Load_balance_example-inboundSequence"
    onError="someOtherSequence"/>  <!-- không tồn tại -->

```

```xml
<!-- Đúng -->
<inboundEndpoint name="Load_balance_example"
    sequence="Load_balance_example-inboundSequence"
    onError="Load_balance_example-inboundErrorSequence"/>

```

### 3.4.4. I — Tách Riêng Local Entry Theo Chức Năng

Không nhét tất cả cấu hình vào một local entry:

```xml
<!-- Sai: Một local entry chứa tất cả -->
<localEntry key="AllConfig">
    <!-- Kafka config, backend URL, token, ... -->
</localEntry>

<!-- Đúng: Tách riêng -->
<localEntry key="KafkaConnection">...</localEntry>
<localEntry key="BackendConfig">...</localEntry>

```

### 3.4.5. D — Phụ Thuộc Config, Không Hardcode

Sequence phụ thuộc vào `local-entries` và <span class="inline-flex items-center gap-0.5 rounded-md align-middle text-sm font-medium transition-[opacity,background-color] cursor-pointer hover:bg-gray-500/20 select-text translate-y-[-1px]" draggable="true"><span class="inline-flex break-all">config.properties</span></span> — không hardcode URL hay token:

```xml
<!-- Sai -->
<address uri="http://192.168.0.133:8080/api/v1/plannings"/>

<!-- Đúng -->
<address uri="{$ctx:backend.planning.url}"/>

```

## 3.5. Luồng Xác Thực Chuẩn Trong Hệ Thống

### 3.5.1. Luồng Chuẩn Từ Client Đến Backend

```html
Client
  → [1] Lấy token từ WSO2 IS (/oauth2/token)
  → [2] Gọi API qua WSO2 APIM với Bearer token
  → [3] APIM validate token với WSO2 IS (introspect / JWT verify)
  → [4] APIM route request vào WSO2 MI
  → [5] MI set header Authorization khi gọi backend
  → [6] Backend (Spring Boot) validate JWT

```

### 3.5.2. Luồng Kafka Consumer Không Đi Qua APIM

```html
Kafka Topic (test_topic_01)
  → [1] MI Inbound Endpoint consume message
  → [2] MI Inbound Sequence xử lý
  → [3] MI set header Authorization: Bearer <token>
  → [4] MI gọi Backend API
  → [5] Backend validate JWT
  → [6] Kết quả: published_topic hoặc error_topic

```

<p class="callout warning">**Luồng Kafka consumer bypass APIM Gateway — token phải được quản lý thủ công trong MI (lấy từ IS hoặc dùng service account token).**</p>

### 3.5.3. Không Được Phép

```xml
<!-- Sai: Gọi backend không có Authorization header -->
<call>
    <endpoint>
        <address uri="http://192.168.0.133:8080/api/v1/plannings"/>
    </endpoint>
</call>

<!-- Sai: API tại APIM không bật Security Scheme -->
<!-- Mọi API publish lên APIM đều phải bật OAuth2 hoặc API Key -->

```

### 3.5.4. Luồng Token Trong Kafka Consumer (Chuẩn)

```xml
<!-- Bước 1: Lấy token từ config -->
<property name="BACKEND_TOKEN"
    expression="get-property('conf:backend.auth.token')"
    scope="default"/>

<!-- Bước 2: Set Authorization header -->
<header name="Authorization" scope="transport"
    expression="fn:concat('Bearer ', get-property('BACKEND_TOKEN'))"/>
<property name="Content-Type" value="application/json" scope="transport"/>

<!-- Bước 3: Gọi backend -->
<call>
    <endpoint>
        <address uri="{$ctx:backend.planning.url}">
            <suspendOnFailure>
                <initialDuration>1000</initialDuration>
                <progressionFactor>2.0</progressionFactor>
                <maximumDuration>60000</maximumDuration>
            </suspendOnFailure>
        </address>
    </endpoint>
</call>

```

## 3.6. Cấu Hình Xác Thực WSO2 APIM &amp; IS

### 3.6.1. WSO2 IS — Key Manager

WSO2 IS đóng vai trò **Key Manager** cho APIM:

<table border="1" id="bkmrk-ch%E1%BB%A9c-n%C4%83ng-endpoint-w" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 50.0542%;"></col><col style="width: 50.0542%;"></col></colgroup><tbody><tr><th class="bg-gray-500/20 px-6 py-2 text-left font-medium">Chức năng</th><th class="bg-gray-500/20 px-6 py-2 text-left font-medium">Endpoint WSO2 IS</th></tr><tr><td class="px-6 py-2">Lấy Access Token</td><td class="px-6 py-2">`POST /oauth2/token`</td></tr><tr><td class="px-6 py-2">Introspect Token</td><td class="px-6 py-2">`POST /oauth2/introspect`</td></tr><tr><td class="px-6 py-2">Lấy Public Key (JWKS)</td><td class="px-6 py-2">`GET /oauth2/jwks`</td></tr><tr><td class="px-6 py-2">Revoke Token</td><td class="px-6 py-2">`POST /oauth2/revoke`</td></tr></tbody></table>

**Lấy token từ IS (client\_credentials):**

```html
POST https://<IS_HOST>:9443/oauth2/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <base64(clientId:clientSecret)>

grant_type=client_credentials&scope=<scope>

```

### 3.6.2. WSO2 APIM — API Gateway Security

Mọi API publish lên APIM phải cấu hình:

<table border="1" id="bkmrk-thu%E1%BB%99c-t%C3%ADnh-gi%C3%A1-tr%E1%BB%8B-c" style="border-collapse: collapse; width: 100%;"><colgroup><col style="width: 50.0542%;"></col><col style="width: 50.0542%;"></col></colgroup><tbody><tr><th class="bg-gray-500/20 px-6 py-2 text-left font-medium">Thuộc tính</th><th class="bg-gray-500/20 px-6 py-2 text-left font-medium">Giá trị chuẩn</th></tr><tr><td class="px-6 py-2">Security Type</td><td class="px-6 py-2">OAuth2 hoặc API Key</td></tr><tr><td class="px-6 py-2">Token Validation</td><td class="px-6 py-2">IS Introspection / JWT verify</td></tr><tr><td class="px-6 py-2">Throttling</td><td class="px-6 py-2">Theo chính sách dự án</td></tr><tr><td class="px-6 py-2">CORS</td><td class="px-6 py-2">Cấu hình theo môi trường</td></tr></tbody></table>

### 3.6.3. JWT Claims Chuẩn Của Hệ Thống

Token JWT cấp bởi WSO2 IS phải chứa:

```json
{
  "sub": "admin",
  "role": "ADMIN",
  "iat": 1771917707,
  "exp": 1772004107,
  "iss": "https://<IS_HOST>:9443/oauth2/token",
  "aud": "...client_id...",
  "scope": "..."
}

```

## 3.7. Cách Thực Hiện / Quy Trình

### Quy Trình Thêm Xác Thực Cho Luồng Mới

**Bước 1:** Đăng ký OAuth2 Application trong WSO2 IS

- Tạo Service Provider
- Lấy `clientId` và `clientSecret`

**Bước 2:** Cấu hình token trong

```yaml
backend.auth.token=<access_token_từ_IS>
backend.planning.url=http://192.168.0.133:8080/api/v1/plannings

```

**Bước 3:** Trong Sequence, đọc token từ config

```xml
<property name="BACKEND_TOKEN"
    expression="get-property('conf:backend.auth.token')"/>
<header name="Authorization" scope="transport"
    expression="fn:concat('Bearer ', get-property('BACKEND_TOKEN'))"/>

```

**Bước 4:** Publish API lên APIM với OAuth2 Security

- Import OpenAPI spec từ `resources/api-definitions/`
- Enable Security: **OAuth2**
- Set Key Manager: **WSO2 IS**

**Bước 5:** Test luồng xác thực end-to-end

- Lấy token từ IS
- Gọi API qua APIM với Bearer token
- Verify log trong MI: `STATUS`, `HTTP_STATUS`, `PAYLOAD`

[![image.png](https://docs.lifetex.vn/uploads/images/gallery/2026-02/scaled-1680-/mCVimage.png)](https://docs.lifetex.vn/uploads/images/gallery/2026-02/mCVimage.png)

## 3.8. Ví Dụ Minh Họa

### Ví Dụ Sai — Sequence Không Có Authorization Header

```xml
<sequence name="myFlow-inboundSequence">
    <call>
        <endpoint>
            <address uri="http://192.168.0.133:8080/api/v1/plannings"/>
        </endpoint>
    </call>
    <respond/>
</sequence>

```

**Sai vì:**

- Không set `Authorization: Bearer` header
- Không log request/response
- Backend sẽ reject với 401 Unauthorized
- Không xử lý HTTP status response

### Ví Dụ Sai — Hardcode Token Trong Sequence

```xml
<header name="Authorization" scope="transport"
    value="Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiJ9..."/>

```

**Sai vì:**

- Token hardcode sẽ hết hạn, phải sửa code mỗi lần
- Lộ thông tin xác thực trong source code
- Không thể thay đổi theo môi trường (dev / staging / prod)

### Ví Dụ Đúng — Sequence Xác Thực Và Gọi Backend Chuẩn

```xml
<sequence name="Load_balance_example-inboundSequence">

    <!-- 1. Lưu payload gốc -->
    <property name="PAYLOAD" expression="json-eval($)" scope="default"/>
    <log level="custom">
        <property name="STATUS"  value="[KafkaConsumer] Message nhận được từ Kafka"/>
        <property name="TOPIC"   value="test_topic_01"/>
        <property name="PAYLOAD" expression="get-property('PAYLOAD')"/>
    </log>

    <!-- 2. Set Authorization header từ config -->
    <property name="BACKEND_TOKEN"
        expression="get-property('conf:backend.auth.token')" scope="default"/>
    <header name="Authorization" scope="transport"
        expression="fn:concat('Bearer ', get-property('BACKEND_TOKEN'))"/>
    <property name="Content-Type" value="application/json" scope="transport"/>

    <!-- 3. Gọi backend -->
    <call>
        <endpoint>
            <address uri="{$ctx:backend.planning.url}">
                <suspendOnFailure>
                    <initialDuration>1000</initialDuration>
                    <progressionFactor>2.0</progressionFactor>
                    <maximumDuration>60000</maximumDuration>
                </suspendOnFailure>
            </address>
        </endpoint>
    </call>

    <!-- 4. Kiểm tra phản hồi -->
    <property name="HTTP_STATUS"   expression="$axis2:HTTP_SC"  scope="default"/>
    <property name="API_RESPONSE"  expression="json-eval($)"    scope="default"/>
    <log level="custom">
        <property name="STATUS"      value="[KafkaConsumer] Phản hồi Backend"/>
        <property name="HTTP_STATUS" expression="get-property('HTTP_STATUS')"/>
    </log>

    <!-- 5. Phân loại kết quả -->
    <filter xpath="get-property('HTTP_STATUS') = '200' or get-property('HTTP_STATUS') = '201'">
        <then>
            <kafkaTransport.publishMessages configKey="KafkaConnection">
                <topic>processed_topic</topic>
            </kafkaTransport.publishMessages>
        </then>
        <else>
            <kafkaTransport.publishMessages configKey="KafkaConnection">
                <topic>error_topic</topic>
            </kafkaTransport.publishMessages>
        </else>
    </filter>

</sequence>

```

> **\[Chỗ này thêm ảnh: WSO2 MI log viewer — hiển thị STATUS, HTTP\_STATUS sau khi gọi backend thành công\]**

### Ví Dụ Đúng — API APIM Với OAuth2 Security

```yaml
# resources/api-definitions/KafkaProducerApi.yaml
openapi: 3.0.0
info:
  title: Kafka Producer API
  version: 1.0.0
paths:
  /kafka-producer:
    post:
      summary: Publish message lên Kafka
      security:
        - OAuth2: []          # Bắt buộc xác thực OAuth2 tại APIM
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        '200':
          description: Thành công
components:
  securitySchemes:
    OAuth2:
      type: oauth2
      flows:
        clientCredentials:
          tokenUrl: https://<IS_HOST>:9443/oauth2/token
          scopes: {}

```

## 3.9. Checklist Áp Dụng

Trước khi commit hoặc tạo PR:

**Naming**

- [ ]  API artifact: `<Name>Api.xml`
- [ ]  Inbound Sequence: `<flow>-inboundSequence.xml`
- [ ]  Error Sequence: `<flow>-inboundErrorSequence.xml`
- [ ]  Connection Entry: `<Name>Connection.xml`

**Token &amp; Xác Thực**

- [ ]  Không hardcode Bearer token trong Sequence
- [ ]  Token lấy từ <div>  
    </div><span class="inline-flex items-center gap-0.5 rounded-md align-middle text-sm font-medium transition-[opacity,background-color] cursor-pointer hover:bg-gray-500/20 select-text translate-y-[-1px]" draggable="true"><span class="inline-flex break-all">config.properties</span></span> hoặc `local-entries`
- [ ]  Mọi lời gọi backend đều có `Authorization: Bearer` header
- [ ]  API publish lên APIM đã bật OAuth2 Security
- [ ]  Key Manager trỏ đúng về WSO2 IS

**Logging**

- [ ]  Sequence log `STATUS`, `PAYLOAD` khi nhận request
- [ ]  Sequence log `HTTP_STATUS`, `API_RESPONSE` sau khi gọi backend
- [ ]  Error Sequence log `ERROR_CODE`, `ERROR_MESSAGE`, `ORIGINAL_PAYLOAD`

**Separation of Concerns**

- [ ]  API Layer không chứa logic điều phối phức tạp
- [ ]  Error handling nằm trong Error Sequence riêng
- [ ]  Kết nối Kafka trong `local-entries`, không khai báo lại trong Sequence
- [ ]  URL backend lấy từ config, không hardcode

**Architecture**

- [ ]  Inbound Endpoint trỏ đúng `sequence` và `onError`
- [ ]  Luồng thành công publish vào `processed_topic`
- [ ]  Luồng lỗi publish vào `error_topic`
- [ ]  Không có luồng nào bỏ qua xác thực với backend

## 3.10. Cấu Trúc Chuẩn Của Một Sequence Xử Lý Nghiệp Vụ

### 3.10.1. Mục Tiêu

Quy định cấu trúc thống nhất của một `inboundSequence` nhằm:

- Dễ đọc và dễ review
- Tách rõ: **nhận dữ liệu → xác thực → gọi backend → phân loại kết quả**
- Tránh thiếu log hoặc thiếu xử lý HTTP status
- Chuẩn hóa cách publish kết quả vào Kafka topic

### 3.10.2. Cấu Trúc Bắt Buộc

Một `inboundSequence` phải theo thứ tự:

```html
1. Lưu payload gốc vào property
2. Log thông tin message nhận được
3. Set Authorization header (Bearer token)
4. Gọi backend API (<call>)
5. Lưu HTTP status và response
6. Log phản hồi backend
7. Phân loại theo HTTP status (filter)
   - 2xx → publish vào processed_topic
   - 4xx → đóng gói lỗi dữ liệu → publish vào error_topic
   - 5xx → đóng gói lỗi backend → publish vào error_topic

```

### 3.10.3. Quy Định Chi Tiết Từng Bước

**1. Lưu payload:**

```xml
<property name="PAYLOAD" expression="json-eval($)" scope="default"/>
```

**2. Log input:**

```xml
<log level="custom">
    <property name="STATUS"  value="[KafkaConsumer] Message nhận được từ Kafka"/>
    <property name="PAYLOAD" expression="get-property('PAYLOAD')"/>
</log>
```

**3. Set header xác thực (từ config, không hardcode):**

```xml
<property name="BACKEND_TOKEN"
    expression="get-property('conf:backend.auth.token')"/>
<header name="Authorization" scope="transport"
    expression="fn:concat('Bearer ', get-property('BACKEND_TOKEN'))"/>
```

**4. Gọi backend:**

```xml
<call>
    <endpoint>
        <address uri="{$ctx:backend.planning.url}">
            <suspendOnFailure>
                <initialDuration>1000</initialDuration>
                <progressionFactor>2.0</progressionFactor>
                <maximumDuration>60000</maximumDuration>
            </suspendOnFailure>
        </address>
    </endpoint>
</call>
```

**5–6. Lưu và log phản hồi:**

```xml
<property name="HTTP_STATUS"  expression="$axis2:HTTP_SC"  scope="default"/>
<property name="API_RESPONSE" expression="json-eval($)"    scope="default"/>
<log level="custom">
    <property name="HTTP_STATUS"    expression="get-property('HTTP_STATUS')"/>
    <property name="RESPONSE_BODY"  expression="get-property('API_RESPONSE')"/>
</log>
```

**7. Phân loại kết quả:**

```xml
<filter xpath="get-property('HTTP_STATUS') = '200' or get-property('HTTP_STATUS') = '201'">
    <then>
        <!-- publish processed_topic -->
    </then>
    <else>
        <filter xpath="get-property('HTTP_STATUS') >= '400' and get-property('HTTP_STATUS') &lt; '500'">
            <then>
                <!-- 4xx: lỗi dữ liệu → error_topic -->
            </then>
            <else>
                <!-- 5xx: lỗi backend → error_topic -->
            </else>
        </filter>
    </else>
</filter>
```

### 3.10.4. Ví Dụ Sequence Sai

**Sai vì:** Không có Bearer token, không log, không phân loại HTTP status, không publish kết quả.

### 3.10.5. Checklist Review Sequence

Khi review một `inboundSequence`:

- [ ]  Có lưu `PAYLOAD` vào property trước khi xử lý
- [ ]  Có log thông tin nhận message
- [ ]  Có set `Authorization: Bearer` header (từ config, không hardcode)
- [ ]  Có log HTTP status sau khi gọi backend
- [ ]  Có phân loại HTTP status: 2xx / 4xx / 5xx
- [ ]  2xx → `processed_topic`
- [ ]  4xx và 5xx → `error_topic`
- [ ]  Không xử lý lỗi kỹ thuật trong sequence này (để cho Error Sequence)

*Tài liệu này thuộc phạm vi quản lý của **Core Team** — mọi thay đổi phải được Core Team phê duyệt.*

### <button class="flex items-center font-medium justify-center p-1 rounded bg-primary text-primary-foreground hover:bg-primary-hover duration-100 transition-colors absolute z-10" data-tooltip-id="689024a2-4aad-4468-848d-b7aafee79647" title="Comment on this line"><svg aria-hidden="true" class="lucide lucide-message-square-plus h-3 w-3 stroke-1" fill="none" height="24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewbox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z"></path><path d="M12 8v6"></path><path d="M9 11h6"></path></svg></button>  