Thuế SSO: Vì sao OIDC và SAML thường phải trả phí
Tìm hiểu vì sao app self-hosted miễn phí vẫn khóa OIDC và SAML trong gói trả phí, cùng checklist cần kiểm tra trước khi cài để tránh phí SSO.
Thuế SSO
“Thuế SSO” trong phần mềm tự host là mô hình trong đó ứng dụng được cung cấp miễn phí, còn single sign-on (SSO) là tính năng duy nhất bạn phải trả tiền để sử dụng. Bạn có thể chạy toàn bộ ứng dụng trên VPS của mình mà không cần license key và không bị giới hạn số lượng seat. Sau đó, bạn mở trang authentication trong tài liệu và thấy OpenID Connect (OIDC) hoặc SAML (security assertion markup language) nằm trong gói trả phí.
Điều này quan trọng hơn một tính năng thông thường bị đặt sau paywall, vì SSO giúp một nhóm dịch vụ tự host hoạt động như một hệ thống thống nhất. Identity provider (IdP) cung cấp một account cho mỗi người, một password policy, một nơi để bật multi-factor authentication (MFA), và một nơi để vô hiệu hóa account. Nếu không có IdP, mỗi app vẫn duy trì một user database riêng, và bạn phải quản lý từng database theo cách thủ công.
Mô hình này đã tồn tại đủ lâu để có một bảng công khai. SSO Wall of Shame tại sso.tax liệt kê các vendor tính thêm khoản phí lớn cho single sign-on, với các mục bắt đầu từ 2018. Tác giả đặt ra một ngưỡng hợp lý: "Nếu hỗ trợ SSO của bạn chỉ làm giá tăng 10%, bạn sẽ không có tên trong danh sách này." Phần lớn các mục trong danh sách là phần mềm closed source. Logic định giá tương tự hiện cũng xuất hiện trong các dự án open source mà bạn tự host.
Vì sao các maintainer đặt single sign-on sau gói trả phí
Có hai lý do, và cả hai đều chính đáng. SSO tốn nhiều chi phí hỗ trợ, đồng thời là một trong số ít tính năng mà các tổ chức lớn sẵn sàng trả tiền.
Chi phí hỗ trợ là có thật vì việc tích hợp identity không bao giờ hoàn tất. Mỗi IdP định dạng claims hơi khác nhau. Việc ánh xạ group, thời hạn session, redirect URL và độ lệch clock đều có thể gây ra lỗi đăng nhập. Một lỗi đăng nhập có thể khóa toàn bộ người dùng cùng lúc, nên các ticket này cần được xử lý khẩn cấp. Sau đó sẽ phát sinh các yêu cầu tiếp theo: nested group, ánh xạ role, tự động provisioning bằng SCIM (system for cross-domain identity management), và audit log để đội compliance xem xét.
Doanh thu là vấn đề số học, không phải ác ý. Một công ty không thể kết nối app với IdP của mình sẽ không triển khai app đó. Vì vậy, SSO tạo ra ranh giới rõ ràng giữa người dùng trả tiền và người dùng không trả tiền. Một dự án open core phải đặt ranh giới đó ở đâu đó. SSO phù hợp với vị trí này hơn gần như mọi tính năng khác, nên rất nhiều dự án chọn nó.
Có một điểm cần đính chính cho phàn nàn thường gặp: hãy kiểm tra changelog trước khi cho rằng một tính năng đã bị gỡ bỏ, vì việc gỡ bỏ sẽ xuất hiện trong release notes. Trong các dự án tôi kiểm tra cho bài viết này, các tính năng SSO trả phí đã được xây dựng cho gói trả phí ngay từ đầu. Tôi không tìm thấy trường hợp nào SSO miễn phí đang hoạt động bị rút lại. Grafana là một ví dụ điển hình. Trang SAML của sản phẩm có ghi chú một dòng: "Available in Grafana Enterprise and Grafana Cloud", trong khi OAuth chung với issuer của bạn vẫn hoạt động trong bản open source.
Chi phí thực tế của SSO tax
Tiền chỉ là phần nhỏ hơn. Các gói trả phí tính theo user, nên hóa đơn tăng theo quy mô team, còn việc host và upgrade vẫn do bạn tự lo.
Chi phí lớn hơn là công việc quản lý identity thủ công. Nó phát sinh ở 4 chỗ.
- Mỗi app có một password store riêng, nên một password bị dùng lại sẽ trở thành lỗ hổng trong mọi app dùng chung password đó.
- Offboarding dựa vào trí nhớ. Bạn phải nhớ mọi service mà một người từng truy cập, và service bị quên chính là service gây ra vấn đề.
- MFA phải cấu hình riêng trong từng app, nếu app đó có hỗ trợ MFA.
- Shared login, đây mới là điều thực sự xảy ra ở các team nhỏ khi chịu áp lực này.
Ý cuối cần được nói riêng. Khi một team dùng chung một administrator account trong document manager, audit trail ghi cùng một tên cho mọi thao tác, nên bạn không thể biết ai đã xóa hóa đơn. Phân quyền theo user cũng không còn hiệu quả vì chỉ có một user. Đó mới là thiệt hại thực sự của SSO tax: nó đẩy các team nhỏ về một shared account duy nhất, tệ hơn mọi phương án khác.
Nội dung cần kiểm tra trước khi dùng một ứng dụng
Chạy quy trình này trước docker compose up, không phải sau khi ứng dụng đã lưu 400 tài liệu.
- Mở trang xác thực trong tài liệu và đọc ghi chú về các gói ở đầu trang. Tính năng trả phí thường có nhãn hoặc một câu nêu rõ tính khả dụng.
- Xác nhận ứng dụng hỗ trợ OIDC hoặc SAML với issuer của bạn, thay vì chỉ hỗ trợ một danh sách cố định các nhà cung cấp public.
- Kiểm tra việc ánh xạ role và group. Tạo user mới chỉ là một nửa công việc; gán quyền thủ công trong 10 ứng dụng mới là phần khó chịu.
- Kiểm tra ứng dụng có chấp nhận username đã xác thực trong header từ một proxy đáng tin cậy hay không, và bạn có thể chỉ định proxy mà ứng dụng tin cậy hay không.
- Đọc lịch sử licence trong git và kiểm tra xem contributor có ký CLA (contributor licence agreement) hay không.
- Kiểm tra quy trình offboarding. Tìm hiểu API (application programming interface) token và session đang hoạt động sẽ bị xử lý thế nào khi tài khoản IdP bị vô hiệu hóa.
Mục 2 là nguyên nhân khiến nhiều người thất vọng nhất. Nút “Sign in with Google” không phải là OIDC với identity provider của bạn; đó là một integration cố định với một vendor. Hỗ trợ OIDC thực sự sẽ yêu cầu bạn cung cấp issuer URL, còn mọi thông tin khác được lấy qua discovery. Bạn có thể xác nhận phía provider bằng một lệnh.
curl -s https://id.example.com/.well-known/openid-configuration \
| jq '.issuer, .authorization_endpoint, .token_endpoint'Provider hoạt động bình thường sẽ trả về 3 URL. Kết quả trống hoặc lỗi 404 thường có nghĩa là discovery path bị sai. Path này phụ thuộc vào từng provider: Keycloak công bố nó tại /realms/<realm>/.well-known/openid-configuration. Nếu ứng dụng không có trường để nhập issuer URL, ứng dụng không thể kết nối với IdP của bạn, bất kể danh sách tính năng ghi gì.
Mục 6 thường chỉ được phát hiện vài tuần sau khi một người nghỉ việc. Vô hiệu hóa tài khoản trong IdP sẽ chặn các lần đăng nhập mới. Việc này không revoke API token mà ứng dụng đã cấp trước đó, vì ứng dụng tự xác thực token đó và không bao giờ hỏi IdP về token. Vì vậy, offboarding có 2 bước: vô hiệu hóa tài khoản trong IdP, sau đó xóa user hoặc token của user đó trong từng ứng dụng.
Nội dung thực tế của các nhãn cấp
Các thông tin này được đối chiếu với tài liệu riêng của từng dự án vào tháng 8 năm 2026. Hãy bắt đầu với nhóm trả phí.
Grafana ghi SAML là “Available in Grafana Enterprise and Grafana Cloud”, cùng với đồng bộ team và provisioning SCIM. Generic OAuth, GitHub OAuth, LDAP (lightweight directory access protocol) và auth proxy đều có trong bản open source, nên người tự host quy mô nhỏ vẫn có thể đăng nhập qua provider riêng. Gói trả phí bắt đầu tính phí ở SAML, không phải ở toàn bộ tính năng single sign-on. Đây là điểm khác biệt mà cụm từ “SSO tax” thường làm mất đi.
Metabase nói rõ hơn. Tài liệu của họ ghi: “SAML authentication is only available on Pro and Enterprise plans (both self-hosted and on Metabase Cloud).” Bản open source vẫn hỗ trợ đăng nhập bằng mật khẩu và LDAP.
Passbolt đánh dấu tài liệu SSO là Pro và Cloud, nên community edition không có tính năng này. Các provider được tài liệu hóa gồm Keycloak và Entra ID.
Bây giờ là phía còn lại, vì mô hình này không phổ biến ở mọi dự án.
- GitLab Self-Managed ghi “Tier: Free, Premium, Ultimate” trên trang SAML, nên dùng SAML với GitLab của bạn không mất phí.
- Paperless-ngx cấu hình OIDC qua django-allauth với
PAPERLESS_SOCIALACCOUNT_PROVIDERS, ẩn form đăng nhập cục bộ bằngPAPERLESS_DISABLE_REGULAR_LOGINvà ánh xạ claims vào groups bằngPAPERLESS_SOCIAL_ACCOUNT_SYNC_GROUPS. - Planka nhận
OIDC_ISSUER,OIDC_CLIENT_IDvàOIDC_CLIENT_SECRET, đặt scopes mặc định thànhopenid profile emailvà cấp quyền administrator dựa trên role claim bằngOIDC_ADMIN_ROLES. - BookStack chuyển sang dùng
AUTH_METHOD=oidc, sau đó ánh xạ groups từ provider vào các role riêng bằngOIDC_USER_TO_GROUPS=truevàOIDC_GROUPS_CLAIM. - Vaultwarden phát hành “support for SSO with OpenID Connect” trong phiên bản 1.35.0 vào ngày 27 tháng 12 năm 2025, từ pull request của một contributor trước đó đã duy trì tính năng này trong một fork.
- listmonk đã hỗ trợ đăng nhập OIDC cùng với user roles từ phiên bản v4.0.0.
Hãy dùng thông tin này khi lựa chọn, không phải sau khi đã cam kết với một sản phẩm. Một bảng kanban Planka và các lựa chọn Trello tự host khác không xử lý identity theo cùng một cách. BookStack, Wiki.js và Outline cũng vậy. OIDC miễn phí là một tiêu chí bạn có thể cân nhắc, giống như giới hạn storage hoặc mobile client. Nếu bạn vẫn đang lập danh sách, những gì nên tự host trong năm 2026 là điểm bắt đầu hợp lý. Cả trình quản lý tài liệu Paperless-ngx và Vaultwarden hiện đều cung cấp OIDC miễn phí.
Vì sao reverse proxy đặt trước app không phải là single sign-on
Cách xử lý phổ biến là forward auth. Reverse proxy giữ từng request, hỏi authentication service xem browser này đã đăng nhập chưa, rồi chỉ chuyển request đến app nếu đã được xác thực. authentik gọi cơ chế này là proxy provider, với chế độ forward auth cho một ứng dụng riêng lẻ và một chế độ khác cho toàn bộ domain. Authelia và oauth2-proxy cũng thực hiện cùng chức năng.
Một site block của Caddy có dạng như sau, theo ví dụ chính thức của authentik.
app.example.com {
forward_auth http://authentik-outpost:9000 {
uri /outpost.goauthentik.io/auth/caddy
copy_headers X-Authentik-Username X-Authentik-Email X-Authentik-Groups
trusted_proxies private_ranges
}
reverse_proxy app:8000
}Việc viết hoa các header name này rất quan trọng trong Caddy, vì name không khớp sẽ được truyền đi dưới dạng rỗng. Với mỗi request được phê duyệt, outpost đặt X-authentik-username, X-authentik-email, X-authentik-groups và một vài header khác.
Cơ chế này mang lại các lợi ích sau. Không ai truy cập được app nếu chưa đi qua identity provider của bạn. Vì vậy, login form chưa được vá không còn lộ trực tiếp ra Internet, và MFA được áp dụng đồng thời cho mọi thứ nằm sau proxy.
Cơ chế này không cung cấp identity bên trong app. App vẫn có các account riêng và cách riêng để xác định ai đang đăng nhập. Nếu mọi người đều vượt qua proxy rồi truy cập bằng một shared administrator account, bạn có một cửa trước được bảo vệ tốt nhưng phía sau chỉ có một session ẩn danh. Audit log vẫn chỉ hiển thị một name. Permissions vẫn không thể khác nhau giữa những người dùng. Gọi mô hình này là SSO là một sai lầm về bảo mật, vì quy trình offboarding chỉ đúng một nửa: xóa người dùng khỏi IdP sẽ đóng cửa trước, nhưng API token mà người đó tạo bên trong app vẫn tiếp tục hoạt động với bất kỳ ai có thể truy cập trực tiếp vào app.
Bảo vệ xác thực bằng header
Một số app chấp nhận username do proxy gửi đến. Cách này cung cấp danh tính theo từng user mà không cần SSO trả phí. Tên setting khác nhau tùy project.
Grafana gọi tính năng này là auth proxy và mặc định tắt. Tên header mặc định là X-WEBAUTH-USER. Bạn có thể trỏ setting này đến header mà proxy của bạn thiết lập.
[auth.proxy]
enabled = true
header_name = X-authentik-username
header_property = username
auto_sign_up = true
whitelist = 10.0.0.5whitelist là dòng mà nhiều người bỏ qua. Tài liệu Grafana nêu rõ setting này tồn tại để ngăn user giả mạo header. Vì vậy, nó chỉ nên chứa địa chỉ của proxy và không chứa gì khác. Gitea cũng có tính năng tương tự, nhưng dùng tên khác và có giá trị mặc định an toàn hơn.
[security]
ENABLE_REVERSE_PROXY_AUTHENTICATION = true
REVERSE_PROXY_AUTHENTICATION_USER = X-WEBAUTH-USER
REVERSE_PROXY_TRUSTED_PROXIES = 10.0.0.5/32
REVERSE_PROXY_LIMIT = 1REVERSE_PROXY_TRUSTED_PROXIES mặc định là 127.0.0.0/8,::1/128. REVERSE_PROXY_LIMIT là số proxy mà Gitea sẽ tin cậy trong chuỗi. Đặt giới hạn này bằng zero sẽ tắt hoàn toàn việc xử lý header.
Paperless-ngx cung cấp PAPERLESS_ENABLE_HTTP_REMOTE_USER cùng với PAPERLESS_HTTP_REMOTE_USER_HEADER_NAME. Tài liệu của project có cảnh báo áp dụng cho tất cả các setting này:
Cách này cho phép xác thực chỉ bằng cách thêm header Remote-User: <username> vào request. Hãy sử dụng cẩn thận!
Có 2 quy tắc để giữ an toàn cho xác thực bằng header, và cả hai đều liên quan đến khả năng truy cập. Thứ nhất, app phải không thể truy cập được nếu không đi qua proxy. Bất kỳ ai có thể mở socket đến app đều có thể gửi header đó và đăng nhập dưới danh tính của bất kỳ user nào. Trong Docker, ports: ["8000:8000"] publish trên mọi interface. Vì vậy, hãy bind nó vào địa chỉ loopback bằng ports: ["127.0.0.1:8000:8000"], hoặc bỏ port đã publish và đặt proxy trên cùng Docker network. Thứ hai, proxy phải xóa mọi bản sao của header nhận từ client. Như vậy, giá trị duy nhất app nhìn thấy là giá trị do proxy đặt sau khi xác thực.
Hãy kiểm tra cả hai điều kiện. Chạy lệnh thứ nhất từ một máy bên ngoài VPS và lệnh thứ hai ngay trên server.
curl -si -H "Remote-User: admin" http://203.0.113.10:8000/ | head -n 1
ss -ltnp | grep 8000curl phải không kết nối được, còn ss phải in 127.0.0.1:8000 thay vì 0.0.0.0:8000. Dòng đầu tiên là HTTP/1.1 302 Found có nghĩa app đang trả lời trực tiếp trên Internet công cộng. Khi đó, bất kỳ ai cũng có thể đăng nhập dưới danh danh tính của bất kỳ user nào bằng cách chỉ định user đó trong header.
Khi không có SSO miễn phí, hãy chọn giải pháp thay vì phàn nàn
Có 4 lựa chọn, theo thứ tự tôi sẽ thử.
- Chọn app có OIDC. Nếu 2 project làm cùng một việc nhưng một project kết nối miễn phí với identity provider của bạn, đó là khác biệt thực sự về chi phí vận hành.
- Dùng forward auth một cách phù hợp. Với một công cụ quản trị chỉ có 1 account và 1 người vận hành, đặt proxy phía trước là đủ; định danh từng người dùng bên trong app không mang lại thêm lợi ích.
- Trả phí. Nếu app giữ vai trò trung tâm trong công việc và mức giá theo seat phù hợp với quy mô team, khoản tiền đó giúp duy trì project. Phương án thay thế là tự duy trì bằng thời gian buổi tối của bạn.
- Hỏi upstream sau khi đã tìm trong issue tracker. Hỗ trợ OIDC của Vaultwarden xuất phát từ fork của một contributor và một pull request tồn tại trong thời gian dài. Vì vậy, feature request kèm implementation hoạt động đôi khi vẫn được đưa vào phiên bản miễn phí.
Không lựa chọn nào trong số này hoạt động nếu bạn không có identity provider riêng. Đây là thành phần cần xây dựng trước. Chạy authentik trên VPS cung cấp OIDC và SAML provider cùng forward auth outpost được dùng ở trên. Bài so sánh Keycloak, authentik và Zitadel trình bày các đánh đổi nếu bạn chưa muốn cam kết với một lựa chọn.
Lịch sử licence và lý do mục này có trong checklist
Mục cuối trong checklist nói về tương lai, vì cách phân loại tier hiện tại chỉ là một ảnh chụp tại một thời điểm. Hai trường hợp được ghi nhận rõ cho thấy tình hình có thể thay đổi nhanh theo cả hai hướng. HashiCorp áp dụng Business Source License 1.1 cho mọi bản phát hành trong tương lai vào ngày 10 August 2023, trong khi các bản phát hành trước đó vẫn dùng MPL 2.0 (Mozilla Public License). Redis chuyển sang SSPL (server side public license) vào March 2024, sau đó thông báo vào ngày 1 May 2025 rằng Redis 8 cũng được phát hành theo AGPLv3 (GNU Affero General Public License).
Hãy xem cả hai trường hợp này là bằng chứng về cơ chế thay đổi, không phải về động cơ. Licence bạn đọc hôm nay áp dụng cho phiên bản bạn cài hôm nay. Một project nắm giữ toàn bộ copyright có thể tự thay đổi điều khoản của bản phát hành tiếp theo. Vì vậy câu hỏi về CLA có trong checklist: việc chuyển nhượng copyright trên phạm vi rộng là điều cho phép relicence đơn phương.
Hãy tự kiểm tra lịch sử của project trước khi xây dựng hệ thống dựa trên project đó.
git clone --filter=blob:none https://github.com/paperless-ngx/paperless-ngx.git
cd paperless-ngx
git log --follow --oneline -- LICENSEMột danh sách ngắn các commit, chủ yếu từ lần import đầu tiên, là dấu hiệu tốt. Nếu file licence đã được viết lại nhiều lần, bạn nên đọc message của từng commit trước khi lập kế hoạch dựa trên các điều khoản hiện tại.
FAQ
SSO tax là gì?
SSO tax là cách tính phí single sign-on như một tính năng cao cấp, trong khi phần còn lại của sản phẩm được cung cấp miễn phí hoặc có giá thấp. Với phần mềm self-hosted, mô hình này thường là một ứng dụng open source có thể chạy mà không cần licence key, nhưng đăng nhập bằng OIDC hoặc SAML lại chỉ có trong gói trả phí. Tên gọi này bắt nguồn từ SSO Wall of Shame trên sso.tax, nơi theo dõi các vendor tính thêm khoản phí lớn cho tính năng này. Hệ quả với người self-host là mỗi app vẫn giữ database người dùng riêng, nên tài khoản phải được tạo và xóa thủ công.
Reverse proxy dùng forward auth có giống SSO không?
Không. Forward auth bảo vệ cửa vào: proxy kiểm tra với identity provider trước khi chuyển bất kỳ request nào đến app. App phía sau vẫn dùng tài khoản riêng, nên nếu mọi người cùng đăng nhập bằng một thông tin dùng chung, hệ thống chỉ tạo một session ẩn danh duy nhất và audit log chỉ ghi một tên. Chỉ có per-user identity thực sự khi app đọc username từ một header. Grafana auth proxy, reverse proxy authentication của Gitea và PAPERLESS_ENABLE_HTTP_REMOTE_USER của Paperless-ngx đều hỗ trợ cách này. Các thiết lập đó chỉ an toàn khi app không thể được truy cập ngoài proxy, vì header chỉ là một chuỗi văn bản mà bất kỳ client nào cũng có thể gửi.
Những app self-hosted nào có OIDC trong bản miễn phí?
Đã đối chiếu với tài liệu của các project vào tháng 8 năm 2026: Paperless-ngx, Planka, BookStack, Gitea, listmonk và Vaultwarden đều hỗ trợ OIDC trong bản miễn phí. GitLab Self-Managed liệt kê SAML ở Tier: Free. Bản open source của Grafana hỗ trợ OAuth thông thường với issuer riêng của bạn, còn SAML trong Grafana là tính năng Enterprise. Hãy xác nhận trên trang authentication của chính project trước khi cài đặt, vì các danh sách này thay đổi theo từng lần releases.
Có nên trả phí cho gói mở khóa single sign-on không?
Hãy quyết định dựa trên 2 con số: có bao nhiêu người cần tài khoản và nếu không dùng SSO thì bạn phải quản lý thủ công bao nhiêu app. Với 1 hoặc 2 administrator, dùng forward auth trước tài khoản local là đủ, và gói trả phí thường không mang lại nhiều lợi ích. Với team có người vào và rời đi, chỉ cần bỏ sót một tài khoản khi offboarding là chi phí đã có thể cao hơn licence; khoản thanh toán cũng giúp duy trì phần mềm mà bạn đang phụ thuộc. Nếu giá không phù hợp, lựa chọn thực tế là dùng app có sẵn OIDC thay vì tìm cách xử lý vòng quanh một app không hỗ trợ nó.