opencode-gitlab-auth
v3.0.0
Published
GitLab OAuth authentication plugin for OpenCode
Maintainers
Readme
OpenCode GitLab Auth
A GitLab authentication plugin for OpenCode v2 that adds a PKCE OAuth 2.0 method and a
self-managed instance URL prompt to OpenCode's built-in gitlab integration.
Upgrading from 2.x? See Upgrading from 2.x below. Version 3.0 targets OpenCode's plugin SDK v2 and is a breaking change from the 2.x line, which targeted
@opencode-ai/plugin(v1).
Why this plugin exists
OpenCode v2 already ships a built-in gitlab provider (gitlab-ai-provider) and a gitlab integration with
env (GITLAB_TOKEN) and manual API key methods out of the box. This plugin adds what core does not:
- PKCE OAuth 2.0 — browser-based authentication with automatic token refresh, so you don't need to create or manage a Personal Access Token.
- Self-managed instance URL support — a form field on both the OAuth and PAT methods lets you point at a self-hosted GitLab instance instead of gitlab.com.
Everything else — credential storage, scheduling token refresh, and wiring instanceUrl/apiKey into the
gitlab-ai-provider SDK — is handled by OpenCode core once this plugin registers the method.
Features
🔐 Two authentication methods on the gitlab integration
- OAuth 2.0 with PKCE — secure browser-based authentication with automatic token refresh, no client secret required.
- Personal Access Token (PAT) — simple token-based authentication for automation, CI/CD, and headless environments.
🛡️ Security
- PKCE (Proof Key for Code Exchange) — eliminates the need for a client secret in a desktop/CLI context.
- State parameter — CSRF protection on the OAuth redirect.
- Local-only callback — the OAuth redirect is handled by a loopback (
127.0.0.1) HTTP server that closes as soon as the callback is received or the flow times out. - Credential storage and refresh — owned entirely by OpenCode core; this plugin never reads or writes token files itself.
🌐 Flexible instance support
- GitLab.com (default)
- Self-managed GitLab instances, via an
instanceUrlfield on both auth methods
Table of Contents
- Requirements
- Installation
- Quick Start
- Authentication Methods
- Configuration
- Architecture
- Security
- Troubleshooting
- Development
- Upgrading from 2.x
- Contributing
- FAQ
- License
Requirements
- OpenCode v2 (plugin SDK
@opencode/plugin^2.0.0) - Node.js 20.0.0 or higher
- npm 9.0.0 or higher
Installation
Via OpenCode's plugin manager
opencode plugin add opencode-gitlab-authVia config
Add the package to plugins in opencode.json/opencode.jsonc:
{
"$schema": "https://opencode.ai/config.json",
"plugins": ["opencode-gitlab-auth"],
}Note the config key is
plugins(plural) in OpenCode v2, notplugin.
Local development install
git clone https://gitlab.com/vglafirov/opencode-gitlab-auth.git
cd opencode-gitlab-auth
npm install
npm run buildPoint a project's config at the local build instead of npm:
{
"plugins": ["/absolute/path/to/opencode-gitlab-auth"],
}Quick Start
1. Configure OpenCode
{
"plugins": ["opencode-gitlab-auth"],
}2. (Optional) Use your own OAuth application
The plugin ships with a default OAuth application registered as a public PKCE client with redirect URI
http://127.0.0.1:8080/callback, so OAuth works out of the box on gitlab.com. You only need your own
application for a self-managed instance, or if you prefer not to rely on the default one.
- Go to your instance's User Settings > Applications (on gitlab.com: here)
- Create a new application with:
- Name:
OpenCode GitLab Auth - Redirect URI:
http://127.0.0.1:8080/callback(must match exactly) - Confidential: ❌ unchecked
- Scopes:
api
- Name:
- Copy the Application ID (a 64-character hex string)
- Make it available to the OpenCode server process. The plugin reads
GITLAB_OAUTH_CLIENT_IDfrom the environment of the server, which in OpenCode v2 is normally a long-lived background service — so a plainexportin your shell will not reach it. Persist it on the service instead:
opencode service set env GITLAB_OAUTH_CLIENT_ID <your-application-id>
opencode service restartVerify it took effect:
opencode service get env GITLAB_OAUTH_CLIENT_ID"Confidential" must be unchecked. PKCE is designed for public clients and this plugin never sends a
client_secret. A confidential application makes GitLab require one, and the token exchange then fails.
A
.envfile in this repository is not read by the plugin or by OpenCode. SettingGITLAB_OAUTH_CLIENT_IDthere has no effect — useopencode service set env(above) or export the variable before launching a foreground/--standaloneserver.
3. Authenticate
opencodeThen run OpenCode's connect/auth command and choose the gitlab integration. Two methods will be listed:
- GitLab OAuth — browser-based PKCE flow (this plugin)
- GitLab Personal Access Token — token-based auth (this plugin adds the instance URL field; the base method ships with OpenCode core)
Authentication Methods
OAuth 2.0 (Recommended)
How it works
- You select GitLab OAuth and optionally enter a self-managed instance URL.
- The plugin starts a loopback HTTP server on
127.0.0.1:8080and returns an authorization URL to OpenCode. - OpenCode's UI presents the URL for you to open (or opens it for you, depending on the client).
- You approve the request on GitLab; GitLab redirects to the local callback server with an authorization code.
- The plugin verifies the
stateparameter, exchanges the code for tokens using the PKCE verifier, and returns the credential to OpenCode core. - OpenCode core stores the credential and refreshes it automatically before it expires, calling back into this
plugin's
refreshhandler.
sequenceDiagram
participant User
participant OpenCode as OpenCode Core
participant Plugin as This Plugin
participant Browser
participant GitLab
participant LocalServer as Local Callback Server (127.0.0.1:8080)
User->>OpenCode: Select "GitLab OAuth"
OpenCode->>Plugin: authorize(answer)
Plugin->>LocalServer: start()
Plugin->>OpenCode: { url, instructions }
OpenCode->>Browser: Open authorization URL
Browser->>GitLab: Authorization request (PKCE challenge)
GitLab->>Browser: User approves
Browser->>LocalServer: Redirect with code + state
Plugin->>GitLab: POST /oauth/token (code_verifier)
GitLab->>Plugin: access + refresh tokens
Plugin->>OpenCode: Credential.OAuth
OpenCode->>OpenCode: Store credential; schedule refreshAdvantages
- ✅ No long-lived credential to manage yourself
- ✅ Automatic token refresh, handled by OpenCode core
- ✅ Revocable from GitLab settings
- ✅ PKCE eliminates the need for a client secret
Requirements
- A GitLab OAuth application with redirect URI
http://127.0.0.1:8080/callback(see Quick Start) GITLAB_OAUTH_CLIENT_IDenvironment variable set to that application's ID- A way to open a URL in a browser during authentication
Personal Access Token
How to create a PAT
- Go to GitLab User Settings > Access Tokens
- Create a token with the
apiscope and an expiration per your security policy - Copy the token (starts with
glpat-) - Enter it, along with an optional self-managed instance URL, when prompted by OpenCode
Advantages
- ✅ No OAuth app registration needed
- ✅ Works in headless environments and CI/CD
- ✅ No browser required
Disadvantages
- ⚠️ Manual token rotation
- ⚠️ Long-lived credential
- ⚠️ Not validated at entry time. Unlike the previous (v1/2.x) implementation, OpenCode v2's
keyauth method has no hook to test the token against GitLab before saving it. An invalid or expired PAT will only surface as a failure on the first model call. Double-check the token before submitting it.
Configuration
Environment Variables
| Variable | Description | Required | Default |
| ------------------------ | -------------------------------------------------------------------------- | --------- | ------------------------------------- |
| GITLAB_OAUTH_CLIENT_ID | OAuth application ID | For OAuth | Bundled ID (does not work by default) |
| GITLAB_INSTANCE_URL | Default GitLab instance | No | https://gitlab.com |
| GITLAB_TOKEN | Read by core's env auth method for gitlab (not managed by this plugin) | No | — |
Credential storage
Credentials are stored and refreshed entirely by OpenCode core, not by this plugin. This plugin only supplies
the authorize, refresh, and label callbacks that core invokes; it does not read or write any files (no
auth.json, no debug log — both were removed when the v1 auth-loader/token-refresh code was replaced by core's
integration/credential system).
Architecture
Project structure
opencode-gitlab-auth/
├── src/
│ ├── index.ts # Plugin entry point (Plugin.define)
│ ├── instance-url.ts # Instance URL resolution/validation helpers
│ ├── oauth-flow.ts # GitLab OAuth token exchange (code + refresh grants)
│ ├── callback-server.ts # Local node:http server for the OAuth redirect
│ └── pkce.ts # PKCE utilities (verifier & challenge)
├── dist/ # Compiled JavaScript (generated)
├── package.json
├── tsconfig.json
└── README.mdCore components
1. Plugin entry point (index.ts)
Default-exports a Plugin.define({ id, setup }) definition. During setup, it registers two methods on the
gitlab integration via ctx.integration.transform:
- An
oauthmethod (authorize,refresh,label) implementing the PKCE flow described above. - A
keymethod that adds theinstanceUrlform field to the PAT flow OpenCode core already provides.
export default Plugin.define({
id: 'gitlab-auth',
async setup(ctx) {
await ctx.integration.transform((editor) => {
editor.method.update({
integrationID: 'gitlab',
method: {
/* oauth */
},
authorize,
refresh,
label,
});
editor.method.update({
integrationID: 'gitlab',
method: {
/* key */
},
});
});
},
});There is no loader, no token-refresh mutex, and no filesystem access — OpenCode core's credential/integration
system replaces all of it. See the V1→V2 plugin migration guide
for the general shape of this change.
2. OAuth flow (oauth-flow.ts)
GitLabOAuthFlow performs the two GitLab OAuth token-exchange requests this plugin needs:
class GitLabOAuthFlow {
constructor(options: { instanceUrl: string; clientId: string });
async exchangeAuthorizationCode(
code: string,
codeVerifier: string,
redirectUri: string
): Promise<OAuthTokens>;
async exchangeRefreshToken(refreshToken: string): Promise<OAuthTokens>;
}3. Callback server (callback-server.ts)
A minimal node:http server (no external dependencies) that listens on 127.0.0.1:8080 for the OAuth redirect,
parses code/state/error query parameters, renders a success/failure HTML page, and closes itself once the
callback is received or the configured timeout elapses. Includes a simple request counter to bound the number of
requests it will process before refusing further ones.
class CallbackServer {
constructor(options?: { port?: number; host?: string; timeout?: number });
async start(): Promise<void>;
async waitForCallback(): Promise<{ code: string; state: string }>;
getPort(): number;
getCallbackUrl(): string;
async close(): Promise<void>;
}4. PKCE utilities (pkce.ts)
function generateSecret(length?: number): string; // random verifier/state
function generateCodeChallengeFromVerifier(verifier: string): string; // SHA-256, base64urlUses Node's crypto module; no client secret is ever generated or required.
Data flow
flowchart TD
A[User selects GitLab OAuth] --> B[authorize receives instanceUrl answer]
B --> C[Generate PKCE verifier, challenge, state]
C --> D[Start local callback server on :8080]
D --> E[Return authorization URL + instructions to OpenCode]
E --> F[User approves on GitLab in browser]
F --> G[GitLab redirects to local callback server]
G --> H{state matches?}
H -->|No| X[Reject: possible CSRF]
H -->|Yes| I[POST /oauth/token with code_verifier]
I --> J[Return Credential.OAuth to OpenCode core]
J --> K[OpenCode core stores credential]
K --> L[Core calls refresh before expiry]Security
PKCE
Traditional OAuth requires a client secret, which cannot be safely embedded in a CLI/desktop application. PKCE avoids this: the client generates a random verifier, sends its SHA-256 hash (the "challenge") during authorization, and only reveals the verifier during token exchange — a value never transmitted during the browser redirect and therefore not interceptable from it.
State parameter
A random state value is generated per authorization attempt and checked against the value GitLab returns on
redirect, rejecting the flow if they don't match (CSRF protection).
What this plugin does not do
- It does not store, encrypt, or manage any files. Credential persistence, encryption at rest, and refresh scheduling are entirely OpenCode core's responsibility.
- It does not validate Personal Access Tokens before they're saved (see Personal Access Token above).
- It does not open a browser itself — OpenCode's client is responsible for presenting/opening the returned authorization URL.
Reporting security issues
Please report security vulnerabilities to the maintainers privately. Do not open public issues for security concerns.
Troubleshooting
OAuth: invalid_grant on token exchange
The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.
GitLab returns this one generic error for several distinct causes. Check them in this order:
A stale authorization code. This is by far the most common cause. Codes are single-use and are bound to the PKCE verifier of the login attempt that created them, so all of these fail:
- reloading the
127.0.0.1:8080/callbacksuccess page - opening an authorization URL from an earlier, abandoned attempt
- starting a second login while a first one is still pending
Close stray browser tabs pointing at
/oauth/authorizeor/callback, then start a completely fresh login.- reloading the
A custom
GITLAB_OAUTH_CLIENT_IDwhose application is misconfigured. Check which value the server has:opencode service get env GITLAB_OAUTH_CLIENT_IDThat application must register
http://127.0.0.1:8080/callbackexactly (notlocalhost, no trailing slash, no different port), grant theapiscope, and have Confidential unchecked — PKCE requires a public client and this plugin never sends aclient_secret. Unset the variable to fall back to the default application, thenopencode service restart.
The plugin appends the redirect_uri it sent and the client ID actually in effect to this error message — use
that to confirm which cause applies.
OAuth: "The redirect URI included is not valid"
Shown by GitLab in the browser at the authorization step, before any code is issued. The application behind the
client ID in use does not register http://127.0.0.1:8080/callback. If you set GITLAB_OAUTH_CLIENT_ID, add
that exact redirect URI to your application, or unset the variable to use the default application.
OAuth: callback timeout / port 8080 unavailable
The callback server binds a fixed port (8080) to match the redirect URI registered on GitLab. If authentication
never completes:
lsof -i :8080 # macOS/Linux
netstat -ano | findstr :8080 # WindowsFree the port or stop whatever else is using it, then retry.
PAT authentication fails on first model call
Since PATs are no longer validated when entered (see above), a bad token surfaces as a provider error on first use.
Confirm the token is valid and scoped to api:
curl -H "Authorization: Bearer glpat-xxx" https://gitlab.com/api/v4/userSelf-managed GitLab issues
- Enter the instance URL without a trailing slash or path, e.g.
https://gitlab.example.com. - Verify TLS/connectivity independently:
curl -v https://gitlab.example.com/api/v4/version
General
opencode plugin listshould showgitlab-authas active.- Consult OpenCode's own logs/diagnostics for plugin load errors; this plugin no longer writes its own log file.
Development
Setup
git clone https://gitlab.com/vglafirov/opencode-gitlab-auth.git
cd opencode-gitlab-auth
npm install
npm run prepare # install Git hooks
npm run buildAvailable scripts
| Script | Description |
| ---------------------- | -------------------------------- |
| npm run build | Compile TypeScript to JavaScript |
| npm run clean | Remove dist/ |
| npm run rebuild | Clean and build |
| npm test | Run unit tests (Vitest) |
| npm run lint | Check code style (ESLint) |
| npm run lint:fix | Fix code style issues |
| npm run format | Format code with Prettier |
| npm run format:check | Check code formatting |
| npm run prepare | Install Git hooks (Husky) |
Manual verification
Automated tests only cover the pure instance-url.ts helpers. To exercise the plugin end to end:
npm run build- Point a project's
opencode.jsoncpluginsarray at the local build directory (see Local development install). - Confirm
opencode plugin listshowsgitlab-auth. - Run the OAuth flow against gitlab.com, then against a self-managed instance.
- Force a credential refresh (wait for expiry, or shorten it locally) and confirm OpenCode calls back into
refreshsuccessfully. - Run the PAT flow with a self-managed instance URL and confirm the resulting provider call targets that instance.
- Remove/reload the plugin and confirm no listener is left on port 8080.
Commit messages
Follows Conventional Commits, enforced by commitlint via a Husky pre-commit hook:
feat(oauth): add support for custom redirect URIs
fix(server): resolve callback timeout issue
feat!: migrate to opencode plugin SDK v2
BREAKING CHANGE: requires OpenCode v2 and @opencode/plugin ^2.0.0Release process
Releases are automated with semantic-release from the main branch: feat: → minor, fix:/perf:/revert:/
docs: → patch, feat!:/BREAKING CHANGE: → major.
Upgrading from 2.x
Version 3.0 targets OpenCode's plugin SDK v2 (@opencode/plugin) and is not compatible with OpenCode v1 or the
@opencode-ai/plugin package that 2.x depended on. If you're on OpenCode v1, stay on opencode-gitlab-auth@^2.
What changed for users:
- Config key:
"plugin": [...]→"plugins": [...]inopencode.json(c). - Existing v1 credentials in
~/.local/share/opencode/auth.jsonare not migrated. OpenCode v2 stores credentials in its own credential store; re-authenticate once after upgrading. - The debug log at
~/.local/share/opencode/log/gitlab-auth.logno longer exists. - The plugin no longer opens your browser itself; OpenCode's client is responsible for that.
- PATs are no longer validated against the GitLab API before being saved (see Personal Access Token).
- The
gitlabprovider itself (models,gitlab-ai-providerwiring) now ships with OpenCode core — this plugin only adds the OAuth method and the instance-URL form field.
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines on code style, the development workflow, and submitting merge requests.
Quick checklist before opening an MR:
npm run lint
npm run format:check
npm run build
npm testFAQ
Do I need to register my own OAuth app?
Yes. The bundled client ID's redirect URI does not match OpenCode's local callback server, so OAuth will not work
without setting GITLAB_OAUTH_CLIENT_ID to your own application. See Quick Start.
Can I use this with self-managed GitLab?
Yes — enter your instance URL when prompted, for either OAuth or PAT.
Where are my credentials stored?
In OpenCode core's own credential store, not in a file this plugin manages. See Credential storage.
Can I use this in CI/CD?
Yes, via the Personal Access Token method — OAuth requires a browser.
How long do OAuth tokens last, and how are they refreshed?
GitLab OAuth access tokens are short-lived; OpenCode core calls this plugin's refresh handler automatically before
they expire, exchanging the stored refresh token for a new access token.
Changelog
See CHANGELOG.md for version history and release notes.
License
MIT License — see LICENSE file for details.
Copyright (c) 2025 OpenCode GitLab Auth Contributors
Acknowledgments
- Inspired by gitlab-vscode-extension
- OAuth patterns from gitlab-lsp
- Built for OpenCode
- GitLab Duo
- GitLab OAuth
Links
- GitLab Repository
- npm Package
- Issue Tracker
- Contributing Guide
- Changelog
- Agent Guidelines
- OpenCode V1→V2 Plugin Migration Guide
Made with ❤️ for the OpenCode community
