Skip to main content
Beyond CMS engines, Veriqa connects to the systems an employee signs in to every day: DevOps, monitoring, file storage, a forum, an ERP, an LMS, and another identity provider. The OIDC client is free everywhere, but not always built in: GitLab, Keycloak, Grafana, Nextcloud and Discourse ship one, while Odoo and Moodle need a module installed from outside the distribution.

What was verified

Verified on the Email channel, with no changes on the Veriqa side.

GitLab

The barrier. Out of the box the sign-in fails: omniauth_openid_connect puts scope into the grant_type=authorization_code request, where RFC 6749 §4.1.3 does not define it. Veriqa answers invalid_request and GitLab reports:
What to do. Add one flag to the provider args:
The account. Created on first sign-in (JIT) and linked by the Veriqa sub. GitLab treats the address from the email claim as already confirmed and sends no verification mail of its own.
When no email claim arrives, GitLab does not refuse: it creates the user, grants a session and fills in a service address of its own, shaped like temp-email-for-oauth-{login}@gitlab.localhost, while prompting the user to complete the profile.

Keycloak

Here Veriqa is attached to another identity provider as an external OIDC provider: Keycloak accepts the sign-in from Veriqa and then acts as the provider for its own applications. There is barely any configuration. Keycloak imports the Veriqa discovery document and fills in every address itself, jwks_uri included, and turns on signature validation on its own. The barrier is the last name, not the email. The realm user profile requires email, firstName and lastName by default. Veriqa emits family_name only when the channel supplies a last name: a messaging channel takes it from the user’s profile, where it is optional, and Email never supplies one. So the first sign-in stops at the “Update Account Information” form — always on the Email channel, and for some users on the others. What to do. Realm settings → User profile: clear the required flag on firstName and lastName.
Turning off the provider profile review (update.profile.on.first.login = off) does not solve this, although it looks like it does: the user is created, but Keycloak immediately raises the VERIFY_PROFILE required action carrying the same demand — the account exists, the session does not. Only the realm user profile clears the requirement.
Two completion forms in a row. When a sign-in lacks a claim that Veriqa collects itself, Veriqa asks the user for it on its own form before handing the sign-in back. A relying party with a profile completion form of its own — Keycloak, and Logto as well — then shows a second form right after it. For such an application turn the Veriqa form off with ClaimCompletionFormEnabled: false in its entry of Veriqa:OpenIddict:Clients (see Clients). With the form off, the missing claims are not issued and do not block the sign-in: the relying party asks for them on its own form. The phone number and the email are not affected — Veriqa obtains them through separate mechanisms.

Grafana

The barrier. With no email claim Grafana does not refuse outright — it goes looking for the address by a GitHub-compatible convention, requesting {api_url}/emails, which does not exist in OpenID Connect. It gets a 404 and stops the authentication:
The message misleads: the cause is a missing claim, not a network failure. What to do. Make sure email arrives. The Email channel provides it; on a channel that carries no address (Telegram) Veriqa collects it inside the sign-in when email is required, or your own code supplies one — see Getting the phone number and email. The account. Created automatically, with email, name and login all set to the address.
Grafana sends no nonce and does not verify the id_token signature against JWKS — it reads the claims straight from the token body. It also tries to parse the Veriqa access token as a JWT and logs token is not in JWT format: the access token is not a JWT — today it is an opaque reference token, and at the time of the run it was a JWE — and this does not affect sign-in.

Nextcloud

Its barriers are in the transport rather than in the claims, and both are cleared by Nextcloud settings. HTTPS only. Over plain HTTP user_oidc returns a 404 page reading “You must access Nextcloud with HTTPS to use OpenID Connect”. The check passes over HTTPS, in debug mode, or with the allow_insecure_http app setting — the last one is meant for an internal network. Private-address protection. Until allow_local_remote_servers=true is set, Nextcloud will not reach an issuer on a private network. It is hard to diagnose: the return leg answers 403 and the only trace is a line in nextcloud.log:
In production neither setting is needed: the issuer is public and served over HTTPS. Linking — decide before the first sign-in. Nextcloud builds the user id in one of three ways: If you intend to match Nextcloud accounts to your own by sub, clear uniqueUid before the first sign-in: changing the setting later does not rename users who already exist.
Nextcloud verifies the id_token signature against JWKS and does not call /connect/userinfo at all — every claim comes from the id_token. Claims that Veriqa serves only from userinfo will not reach it; enable enrichLoginIdTokenWithUserinfo if you need them.

Discourse

The openid_connect plugin is built into core — there is nothing to install. There are two barriers, and both are cleared by forum settings. By default Discourse does not request an email. The openid_connect_authorize_scope setting contains only openid. For a forum that is a dead end: without an address no account is created and the sign-in stops at the registration form. Set it to openid email profile. Private-address protection. While the issuer sits on a private network Discourse will not reach it: allowed_internal_hosts must list its host. The browser meanwhile receives a generic openid_connect_discovery_error — the cause is visible only with openid_connect_verbose_logging turned on. In production the barrier does not arise.
A failed discovery read is cached. After you fix the setting Discourse keeps answering with the same error for a while, which looks as though the fix did not work. Clear the cache.
What is verified and what is not. Signing in an existing user works, the association is written against the Veriqa sub, and Discourse verifies the id_token signature against JWKS. Creating a new account is not verified: Discourse performs it in a separate request from the browser, guarded by anti-bot checks.

Odoo

The bundled module does not work. The stock auth_oauth only speaks the implicit flow: it asks for response_type=token, sends no nonce, and verifies no signature. Veriqa answers unsupported_response_type: the implicit flow was removed from OAuth 2.1 as unsafe. From the outside it looks like an error page instead of a return to the site. What to install. The auth_oidc module from the OCA repository (the branch matching your Odoo version). In the provider settings choose flow = OpenID Connect (authorization code flow) and fill in the fields that appear: Token URL, JWKS URL and Client Secret. The JWKS URL is what lets Odoo verify the Veriqa signature — and it does.
The module requires the python-jose library, which the official Odoo image does not carry. Its import failure is handled silently, with only a line in the debug log, so without the library the module installs, the sign-in button appears, and the sign-in itself fails with a generic access-denied error. Install python-jose together with the module.
The account. Created automatically — free sign-up is on by default in a fresh Odoo database, so nothing extra needs enabling. The link is written against the Veriqa sub in the oauth_uid field and the address becomes the login. When no address arrives, Odoo makes up a synthetic login of its own and still creates the user.

Moodle

Moodle has no OIDC client of its own; the one in use is the auth_oidc plugin Microsoft ships for Entra ID. Sign-in works, the user is created and the link is written against the Veriqa sub — but three barriers have to be cleared, and two of them you will meet in production as well. The plugin sends a parameter meant for another provider. In every mode except “Microsoft Identity Platform” it adds a resource parameter to the request, and when the setting is left empty it substitutes a hardcoded Microsoft Graph address. Veriqa answers invalid_target: Veriqa registers no RFC 8707 resources, so no value passes. There are two ways through, and the one on the Veriqa side needs no change in Moodle: enable the per-client quirk drop-resource-parameter for this client_id and the parameter is stripped before validation. Nothing consumes it here, so the issued token is the one a client that never sent it would get — no resource is registered and no audience granted.
On the Moodle side there is exactly one way around this — set the provider type to “Microsoft Identity Platform”, which drops the parameter. It works, but it deliberately mislabels the provider in your settings: the plugin offers no generic “other provider” mode without resource. The recommended route is the quirk above.
Protection against private addresses — and against ports. Moodle blocks outbound requests by address (curlsecurityblockedhosts) and also by port: curlsecurityallowedport allows only 443 and 80 by default. Unlike the other systems, this barrier does not go away by itself in production — if your issuer listens on a non-standard port, that port has to be allowed explicitly. The diagnostics are thin: the browser shows “Error in OpenID Connect. Please check logs”, and the real cause (The URL is blocked.) appears only after the plugin’s debug mode is switched on. A colon in the identifier. If Moodle has nothing but the Veriqa sub to build a username from, sign-in stops at “The given username contains invalid characters”: the sub looks like telegram:1234 or email:address, and Moodle does not accept a colon in usernames. Two ways out: request the profile scope, so the name comes from preferred_username, or enable extendedusernamechars. After creation. Moodle sends the new user to a “complete your profile” page: first and last name are mandatory there, and a channel does not always supply them.

The email requirement: four different behaviors

Telegram carries no address, and systems treat its absence differently. The difference is practical: it decides whether you need a substituted address at all. For the same picture across CMS engines, see the CMS integrations overview.

Local testing: every system has its own switch

When you run both on your own machine the issuer ends up on a private network, and almost every system guards against calls there with a flag of its own: The first four barriers disappear in production along with the public issuer. Moodle is the exception: it also restricts the port, so it remains an obstacle on a public address too when the issuer listens on something other than 443 or 80. More on local test setups in Local testing.

Not verified

  • Channels other than Email. On Telegram the expected difference is the address requirement (see the table above).
  • Merging with an account already present locally — apart from Discourse, every system above was verified with a newly created user.
  • Roles, groups and permissions. The user gets the default role; mapping groups from claims was not tested in any of the systems.
  • Repeat sign-in and profile refresh from the provider on subsequent sign-ins.