Experimental. Git-over-HTTP support for the GitLab provider is opt-in.
GitLab OAuth access tokens are opaque (not JWTs), so they cannot be validated locally. The plugin introspects the token at GitLab's /oauth/token/info endpoint and then reads the profile from /api/v4/user. The token must:
token/info's application.uid must be the configured client-id or one of the trusted-audience entries. A token minted for any other GitLab application is rejected;token/info's scope must include every required-scope (default read_user);token/info resource_owner_id matches the /api/v4/user id (subject binding).Personal, Project, and Group access tokens are not OAuth-application tokens; they carry no matching application.uid and are therefore rejected.
[auth] type = OAUTH gitBasicAuthPolicy = OAUTH [plugin "gerrit-oauth-provider-gitlab-oauth"] root-url = https://gitlab.com/ # or https://gitlab.example.com/ for self-managed client-id = ... client-secret = ... enable-git-over-http = true # required-scope = read_user # optional, repeatable; default read_user # trusted-audience = <helper-client-id> # optional, repeatable; see "Token acquisition"
auth.type = OAUTH is required; gitBasicAuthPolicy = OAUTH is shown explicitly so a site-wide override does not route Git/HTTP through password auth.enable-git-over-http = true the GitLab Git path is not installed and only browser login works.enable-git-over-http = true.required-scope (repeatable) is the defense-in-depth scope gate. It defaults to read_user (the minimum for /api/v4/user); set it to api or a narrower custom scope to require more.trusted-audience (repeatable, optional) lists additional GitLab OAuth application ids (application.uids) whose tokens the Git path accepts, beyond client-id. Leave it unset and only browser (client-id) tokens pass — existing deployments are unchanged. Set it to register a separate client (a git credential helper's own client, or a CI client); see “Token acquisition”.token/info and /api/v4/user are called only over https or to a loopback host, so a mistyped root-url cannot leak the token in cleartext. Point root-url at an https GitLab (or a loopback host for local testing).Set enable-token-refresh = true (default false) to make GitLab refresh-capable; GitLab issues a refresh_token for the authorization-code flow by default, so no extra scope is requested. An expired token is renewed on read:
GET /accounts/{id}/oauthtoken) refresh it via OAuthTokenRefresher before returning it; andoauth-token SSH command refreshes on demand and needs only enable-token-refresh on the issuing provider — not enable-git-over-http.Neither refreshes during a Git-over-HTTP request, which validates the presented opaque token via /oauth/token/info as before.
Every Git-over-HTTP request carries an Authorization header:
Authorization: Basic <base64("<username>:<access-token>")>
or
Authorization: Bearer <access-token>
The plugin's GitLabTokenInfoValidator:
GET <root>/oauth/token/info (Bearer) — checks application.uid is a trusted audience (client-id or a trusted-audience entry), scope ⊇ required-scope, and reads resource_owner_id;GET <root>/api/v4/user (Bearer) — reads id/username/email/name/state;resource_owner_id == /api/v4/user id, mapping the user the same way the browser flow does (shared GitLabUserInfoMapper);state is active, so a valid token for a blocked, deactivated, banned, or ldap_blocked account cannot push;401.Successful validations are cached in the oauth_token_validation cache (keyed by a SHA-256 of the token, never the raw token) for the lesser of the token's own expiry (expires_in from token/info) and a hard TTL (60 s by default). Tokens without an expires_in are not cached. Tune it under the plugin-qualified name:
[cache "gerrit-oauth-provider.oauth_token_validation"] maxAge = 60s memoryLimit = 10000
Statistics (the REST/registry name uses hyphens): /a/config/server/caches/gerrit-oauth-provider-oauth_token_validation.
Unlike Google, GitLab has true public OAuth clients (PKCE, loopback, device flow), so off-the-shelf credential helpers such as git-credential-oauth can obtain and refresh tokens for git clone/fetch/push. The Git path accepts a token only if its token/info application.uid is a trusted audience, so the helper's client must be one Gerrit trusts.
Recommended: a separate public helper app + trusted-audience. Register a dedicated non-confidential GitLab OAuth application for the helper (public / PKCE, no client secret), keep Gerrit‘s browser client-id for SSO, and list the helper’s client id in trusted-audience:
[plugin "gerrit-oauth-provider-gitlab-oauth"] client-id = <browser-client-id> # Gerrit browser SSO trusted-audience = <helper-client-id> # git credential helper / CI client enable-git-over-http = true
A public helper app needs no secret to distribute, which is exactly why a normal helper can use it. Gerrit's browser SSO app is typically confidential (has a secret); reusing it for a helper would mean handing that secret to every client, defeating the point of a separate audience.
Alternative: point the helper at Gerrit's client-id. Only appropriate if Gerrit's app is itself usable as a public/PKCE client, or for trusted, secret-capable automation where distributing the client secret is acceptable. For git-credential-oauth, set oauthClientId to that application.
git-credential-oauth endpoint gotcha. Configure the helper under the Gerrit remote host (that is the URL git authenticates against), but point its OAuth endpoints at GitLab — the helper cannot infer a GitLab host from a https://gerrit.example.com/... remote, and a relative /oauth/authorize would resolve against Gerrit, not GitLab:
git config --global credential.https://gerrit.example.com.helper oauth git config --global credential.https://gerrit.example.com.oauthClientId <helper-client-id> git config --global credential.https://gerrit.example.com.oauthScopes read_user git config --global credential.https://gerrit.example.com.oauthAuthURL https://gitlab.example.com/oauth/authorize git config --global credential.https://gerrit.example.com.oauthTokenURL https://gitlab.example.com/oauth/token
The read_user scope above is sized for Gerrit validation only — it is all the validator needs (application.uid + /api/v4/user + required-scope). Add any other scopes your helper or workflow expects (upstream GitLab examples often add read_repository/write_repository); a too-narrow scope surfaces as a helper-side failure, not a Gerrit auth failure.
Either way, request at least read_user (or whatever required-scope demands). Personal/Project/Group access tokens carry no application.uid and are rejected.
| Symptom | Cause |
|---|---|
401, “Token belongs to a different GitLab application” | The token's application.uid is neither client-id nor a trusted-audience entry. Use a token issued for a trusted app, or add its client id to trusted-audience. |
401, “Token is missing the required scope” | The token lacks a required-scope value (default read_user). |
401, subject mismatch | token/info resource_owner_id did not match the /api/v4/user id. |