Verified on “Site Management: Business” with the Telegram and Email channels: sign-in, automatic
user creation, linking in
b_socialservices_user, and the button in the stock form.What will not work
The built-inCOpenIDClient is OpenID 2.0, not OpenID Connect: it has openid.ns and
check_authentication, but no id_token, no JWKS and no PKCE. It is fundamentally incompatible
with Veriqa. By default the sign-in form shows exactly the services of that era — Livejournal,
Rambler, Blogger, Mail.Ru OpenID.
The marketplace module anighr.openidconnect has not been tested: the marketplace requires a
license key.
The path that works — a socialservices provider
The whole scheme fits into stock extension points:
- a handler for the
OnAuthServicesBuildListevent returns the provider descriptor (ID/CLASS/NAME/ICON) — after that the button shows up in the sign-in form by itself, with no template changes; - the provider class returns the link to
/connect/authorizeand handles the return; CSocServAuth::AuthorizeUser()takes care of finding, creating and linking — the link lands inb_socialservices_user.
apple.php in the socialservices module.
Two settings without which sign-in never completes
AuthorizeUser returns SOCSERV_REGISTRATION_DENY, and from the outside that does
not look like a configuration problem — it looks like a broken sign-in. On a production site
mind the side effect: the first setting also enables Bitrix’s own registration.
The CUser::GetList trap — required reading
CUser::GetList does not understand the D7 '=FIELD' syntax: it silently drops filter keys
it does not recognize and behaves as if there were no filter at all. Fetch() then returns the
first row — the user with id 1, the administrator. Code that means “found an existing user and
signed them in” turns into signing in as the administrator on every successful OIDC sign-in.
There is no bug in Bitrix here: the platform writes those keys correctly. What breaks is mixing
dialects in the integration code. The rule is simple: legacy keys with the legacy API, or D7
syntax with
UserTable::getList() — never mixed. And look up “your” users through the links in
b_socialservices_user, not through b_user fields.
Three more framework pitfalls
getUrl()is called twice per form render (GetFormHtmlandGetOnClickJs) — memoize thestateand PKCE generation per request, or the second call overwrites the first.- The core session is already open inside the provider, so you do not need one of your own.
session_write_close()cuts off the Bitrix session in the middle of rendering the page. session_name()changes for the whole process. Open your own session without restoring the name beforeprolog_before.php, and the core opens a session under the wrong cookie: the sign-in “disappears” on the next request.
main.new_user_email_required. That is gentler than Drupal and Joomla, where the address is
needed on every sign-in.
When the address does not arrive as a claim (Telegram does not provide one), substitute a
synthetic one using the same convention as on the other engines:
{channel}-{id}@{channel}.veriqa.invalid. The .invalid domain is reserved by
RFC 2606 — mail to such an address goes nowhere, and the
account gets neither notifications nor password recovery. A deliberate trade-off, not a free
workaround.
On the Email channel the address arrives as a claim and no synthesis is needed.
The ownership marker is set by the platform: an account created through the framework gets
b_user.EXTERNAL_AUTH_ID = socservices, put there by CSocServAuth::AuthorizeUser itself. Your
own provider identifier never reaches that field, so searching by it is pointless.A pleasant side effect
The stockcheckOldUser recognizes “old scheme” accounts (EXTERNAL_AUTH_ID plus XML_ID
directly in b_user) and moves them into the link table without creating duplicates — migrating
from an older integration happens on its own.