Set up the Ctadel GitLab OAuth Application
GitLab doesn't have a true equivalent of GitHub Apps (where the bot is its own identity). The closest practice is an OAuth Application: each customer authorizes Ctadel and we hold a refreshable access token. The trade-off versus GitHub Apps: the token authorizes as the authorizing user, but unlike a PAT it can be refreshed and doesn't require the user to copy-paste anything.
Step 1, create the OAuth Application
-
Go to gitlab.com → User Settings → Applications → Add new application. (For a group-scoped app, use Group → Settings → Applications instead — but a user-scoped app works fine for SaaS where each customer authorizes individually.)
-
Fill in:
- Name: e.g.
Ctadel Scanner. - Redirect URI:
https://<your-ctadel-host>/api/integrations/gitlab/callback. Must match exactly — GitLab refuses the callback otherwise. - Confidential: ✅ checked (we hold the client secret server-side).
- Scopes:
api,read_repository. Don't grantsudoor admin scopes.
- Name: e.g.
-
Click Save application. GitLab shows you the Application ID and Secret once. Copy both — the Secret is shown only at creation time.
Step 2, set the env vars
In the deployment env file:
CTADEL_GITLAB_OAUTH_CLIENT_ID=<application id>
CTADEL_GITLAB_OAUTH_CLIENT_SECRET=<application secret>
NEXT_PUBLIC_CTADEL_GITLAB_OAUTH_CLIENT_ID=<application id>
The redirect URI is automatically derived from APP_BASE_URL + /api/integrations/gitlab/callback,
so make sure APP_BASE_URL is set to your Ctadel host (e.g. https://app.acme.ctadel.eu).
Step 3, restart the stack
docker compose up -d --force-recreate csm-orchestrator csm-frontend
Look for the log line:
GitLab OAuth initialized (redirect=https://.../api/integrations/gitlab/callback)
Step 4, smoke-test
- In Ctadel Settings → Integrations, click Connect Ctadel to GitLab.
- GitLab prompts to authorize the Ctadel app on your account. Approve.
- Back in Ctadel you'll see a "GitLab connected — finish the setup" banner with a one-time webhook secret. Copy it.
- On each GitLab project you want to scan, go to Settings → Webhooks → Add new webhook:
- URL:
https://<your-ctadel-host>/webhook/git - Secret token: paste the webhook secret from Step 3.
- Triggers: ✅ Push events, ✅ Merge request events.
- URL:
- Push a trivial commit. The scan should fire.
Why webhooks are still per-project on GitLab
GitLab OAuth doesn't give us a global webhook stream like GitHub Apps do. Each project keeps its own webhook configuration. The webhook secret we generate is the same for all of a customer's projects (it's stored once per integration) so the customer can copy-paste the same value in each project.
Permissions surface
| Scope | Why |
|---|---|
api | Post MR comments + commit statuses. |
read_repository | Clone the repo at the scanned commit. |
We do not request sudo, admin_mode, read_user, or any write scopes outside MR/status.
Token rotation
GitLab access tokens are short-lived (2h by default) and refreshed automatically. GitLab
rotates the refresh token on every refresh, so Ctadel persists the new pair via the
orchestrator's UpdateGitLabOAuthToken path. If a customer revokes the app from their account,
the next refresh fails and the integration is marked last_error="oauth refresh failed".
Troubleshooting
redirect_uri_mismatchat the authorize step — yourAPP_BASE_URLdiffers from the redirect URI registered on the OAuth Application. They must match byte-for-byte (scheme, host, port, path).invalid_grantat refresh — the refresh token was already rotated by a concurrent call. Mark the integration disabled and have the customer re-authorize.