GitLab OAuth authentication plugin for OpenCode
A secure OAuth 2.0 and Personal Access Token authentication plugin for OpenCode that enables seamless integration with GitLab.com and self-hosted GitLab instances.


- OAuth 2.0 Flow - Secure browser-based authentication with automatic token management
- Personal Access Token (PAT) - Simple token-based authentication for automation and CI/CD
- PKCE (Proof Key for Code Exchange) - Enhanced OAuth security without client secrets
- State Parameter - CSRF attack prevention
- Secure Token Storage - Credentials stored with 600 file permissions
- Automatic Token Refresh - Seamless token renewal via OpenCode's auth system
- Rate Limiting - Built-in protection against abuse (30 requests/minute)
- GitLab.com (default)
- Self-hosted GitLab instances
- Custom instance URL configuration
- Automatic browser opening for OAuth
- Local callback server for seamless authorization
- No manual code copying required
- Comprehensive error handling and user feedback
- Debug logging for troubleshooting
- Installation
- Quick Start
- Authentication Methods
- OAuth 2.0 (Recommended)
- Personal Access Token
- Configuration
- Architecture
- API Reference
- Security
- Troubleshooting
- Development
- Contributing
- License
- Node.js 18.0.0 or higher
- npm 9.0.0 or higher
- OpenCode installed and configured
``bash`
npm install @gitlab/opencode-gitlab-auth
`bashClone the repository
git clone https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-auth.git
cd opencode-gitlab-auth
Quick Start
$3
Add the plugin to your OpenCode configuration:
`json
{
"plugins": ["@gitlab/opencode-gitlab-auth"]
}
`$3
For OAuth authentication, you need to register an OAuth application on GitLab:
1. Go to GitLab User Settings > Applications
2. Create a new application with:
- Name:
OpenCode GitLab Auth
- Redirect URI: http://127.0.0.1:8080/callback
- Confidential: ā
(checked)
- Scopes: api
3. Copy the Application ID (NOT the secret - PKCE doesn't need it)
4. Set the environment variable:`bash
export GITLAB_OAUTH_CLIENT_ID=your_application_id_here
`Or create a
.env file:`bash
GITLAB_OAUTH_CLIENT_ID=your_application_id_here
`$3
Start OpenCode and use the
/connect command:`bash
opencode
In OpenCode:
/connect
`Choose your authentication method:
- GitLab OAuth - Browser-based authentication (recommended)
- GitLab Personal Access Token - Token-based authentication
Authentication Methods
$3
OAuth provides the most secure authentication with automatic token refresh and no need to manage long-lived credentials.
#### How It Works
1. Authorization Request: OpenCode opens your browser to GitLab's authorization page
2. User Consent: You approve the application's access request
3. Callback Handling: GitLab redirects to a local callback server
4. Token Exchange: The authorization code is exchanged for access and refresh tokens using PKCE
5. Secure Storage: Tokens are stored in
~/.local/share/opencode/auth.json with 600 permissions#### OAuth Flow Diagram
`mermaid
sequenceDiagram
participant OpenCode
participant Browser
participant GitLab
participant LocalServer as Local Callback Server
participant Storage as Secure Storage OpenCode->>Browser: 1. Open Authorization URL
(with PKCE challenge)
Browser->>GitLab: 2. Authorization Request
GitLab->>Browser: 3. User Approves
Browser->>LocalServer: 4. Redirect with Auth Code
LocalServer->>GitLab: 5. Token Exchange
(with PKCE verifier)
GitLab->>LocalServer: 6. Access + Refresh Tokens
LocalServer->>Storage: 7. Save Tokens
LocalServer->>OpenCode: 8. Authentication Complete
`#### Advantages
- ā
Most secure method (no long-lived credentials)
- ā
Automatic token refresh
- ā
Revocable from GitLab settings
- ā
No manual token management
- ā
PKCE eliminates need for client secrets
#### Requirements
- Custom OAuth application registered on GitLab
-
GITLAB_OAUTH_CLIENT_ID environment variable set
- Browser access for authorization$3
PAT authentication is simpler but requires manual token management.
#### How to Create a PAT
1. Go to GitLab User Settings > Access Tokens
2. Create a new token with:
- Name:
OpenCode
- Scopes: api
- Expiration: Set according to your security policy
3. Copy the token (starts with glpat-)
4. Enter it when prompted by OpenCode#### Advantages
- ā
Simple setup (no OAuth app registration)
- ā
Works in headless environments
- ā
Suitable for CI/CD pipelines
- ā
No browser required
#### Disadvantages
- ā ļø Manual token rotation required
- ā ļø Long-lived credentials
- ā ļø No automatic refresh
Configuration
$3
| Variable | Description | Required | Default |
| ------------------------ | --------------------- | --------- | -------------------- |
|
GITLAB_OAUTH_CLIENT_ID | OAuth application ID | For OAuth | Bundled ID (limited) |
| XDG_DATA_HOME | Custom data directory | No | ~/.local/share |$3
Credentials are stored in platform-specific locations:
- Linux/macOS:
~/.local/share/opencode/auth.json
- Windows: ~/.opencode/auth.json
- Custom: $XDG_DATA_HOME/opencode/auth.jsonFile permissions are automatically set to
600 (owner read/write only).$3
Debug logs are written to:
- Linux/macOS:
~/.local/share/opencode/log/gitlab-auth.log
- Windows: ~/.opencode/log/gitlab-auth.logLogs include:
- Authentication flow steps
- Token exchange details
- Error messages and stack traces
- Timestamps for all events
Architecture
$3
`
opencode-gitlab-auth/
āāā src/
ā āāā index.ts # Main plugin entry point
ā āāā oauth-flow.ts # OAuth 2.0 flow implementation
ā āāā callback-server.ts # Local HTTP server for OAuth callbacks
ā āāā pkce.ts # PKCE utilities (verifier & challenge)
āāā dist/ # Compiled JavaScript (generated)
āāā .husky/ # Git hooks for code quality
āāā package.json
āāā tsconfig.json
āāā README.md
`$3
#### 1. Plugin Entry Point (
index.ts)The main plugin that integrates with OpenCode's authentication system.
Key Functions:
-
gitlabAuthPlugin() - Main plugin export implementing OpenCode's Plugin interface
- debugLog() - File-based logging that doesn't interfere with UI
- getAuthPath() - Platform-aware auth file path resolution
- saveAuthData() - Secure credential storage with proper permissionsExports:
`typescript
export const gitlabAuthPlugin: Plugin;
export default gitlabAuthPlugin;
`Authentication Hook:
`typescript
interface AuthHook {
provider: 'gitlab';
loader: (auth: () => Promise) => Promise;
methods: [OAuthMethod, PATMethod];
}
`#### 2. OAuth Flow (
oauth-flow.ts)Implements the complete OAuth 2.0 authorization code flow with PKCE.
Class:
GitLabOAuthFlow`typescript
class GitLabOAuthFlow {
constructor(options: OAuthFlowOptions); // Start OAuth authorization
async authorize(): Promise;
// Exchange authorization code for tokens
async exchangeAuthorizationCode(
code: string,
codeVerifier: string,
redirectUri: string
): Promise;
// Refresh access token
async exchangeRefreshToken(refreshToken: string): Promise;
}
`Interfaces:
`typescript
interface OAuthFlowOptions {
instanceUrl: string; // GitLab instance URL
clientId: string; // OAuth client ID
scopes: string[]; // Requested scopes
method: 'auto' | 'code'; // Authorization method
timeout?: number; // Timeout in milliseconds
}interface OAuthTokens {
access_token: string;
refresh_token: string;
expires_in: number;
token_type: string;
scope: string;
created_at: number;
}
interface AuthorizationResult {
code: string; // Authorization code
state: string; // CSRF protection state
codeVerifier: string; // PKCE verifier
}
`Flow Steps:
1. Generate PKCE parameters (verifier & challenge)
2. Generate random state for CSRF protection
3. Build authorization URL with parameters
4. Open browser or provide manual URL
5. Wait for callback with authorization code
6. Verify state parameter matches
7. Exchange code for tokens using PKCE verifier
8. Return tokens for storage
#### 3. Callback Server (
callback-server.ts)Local HTTP server using Fastify to handle OAuth redirects.
Class:
CallbackServer`typescript
class CallbackServer {
constructor(options?: CallbackServerOptions); // Start the server
async start(): Promise;
// Wait for OAuth callback
async waitForCallback(): Promise;
// Get the actual port being used
getPort(): number;
// Get the full callback URL
getCallbackUrl(): string;
// Close the server
async close(): Promise;
}
`Interfaces:
`typescript
interface CallbackServerOptions {
port?: number; // Port to listen on (0 = random)
host?: string; // Host to bind to (default: 127.0.0.1)
timeout?: number; // Timeout in ms (default: 60000)
}interface CallbackResult {
code: string; // Authorization code from GitLab
state: string; // State parameter for verification
}
`Features:
- Rate Limiting: 30 requests per 60 seconds
- Error Handling: Displays user-friendly error pages
- Success Page: Confirms authentication and instructs user
- Automatic Cleanup: Closes server after callback or timeout
- Timeout Protection: Rejects promise after configured timeout
Callback Route:
`
GET /callback?code=xxx&state=yyy
`Response scenarios:
- ā
Success: Returns HTML success page, resolves promise
- ā Error: Returns HTML error page, rejects promise
- ā±ļø Timeout: Rejects promise after timeout period
#### 4. PKCE Utilities (
pkce.ts)Cryptographic functions for OAuth PKCE (Proof Key for Code Exchange).
Functions:
`typescript
// Generate cryptographically secure random string
function generateSecret(length: number = 43): string;// Generate SHA-256 code challenge from verifier
function generateCodeChallengeFromVerifier(verifier: string): string;
// Internal: Base64 URL encoding (RFC 4648 Section 5)
function base64UrlEncode(buffer: Buffer): string;
`PKCE Flow:
1. Code Verifier: Random 43-character string
`typescript
const verifier = generateSecret(43);
// Example: "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
`2. Code Challenge: SHA-256 hash of verifier
`typescript
const challenge = generateCodeChallengeFromVerifier(verifier);
// Example: "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"
`3. Authorization: Send challenge to GitLab
4. Token Exchange: Send verifier to prove ownership
Security:
- Uses Node.js
crypto module for secure randomness
- SHA-256 hashing for challenge generation
- Base64 URL encoding (RFC 4648) for safe transmission
- No client secret required$3
`mermaid
graph TB
OpenCode[OpenCode Core]
Plugin[index.ts Plugin]
AuthHook[AuthHook
- loader
- methods OAuth & PAT]
OAuthFlow[oauth-flow.ts
GitLabOAuthFlow]
PATFlow[Direct API Call
/api/v4/user]
CallbackServer[callback-server.ts
CallbackServer Fastify HTTP]
PKCE[pkce.ts
PKCE Utilities Crypto functions] OpenCode -->|Plugin API| Plugin
Plugin --> AuthHook
AuthHook -->|OAuth Flow| OAuthFlow
AuthHook -->|PAT Flow| PATFlow
OAuthFlow -->|Uses| CallbackServer
OAuthFlow -->|Uses| PKCE
`$3
#### OAuth Authentication Flow
`mermaid
flowchart TD
A[User initiates /connect] --> B[Plugin prompts for instance URL]
B --> C[Generate PKCE verifier & challenge]
C --> D[Start local callback server port 8080]
D --> E[Build authorization URL with:
- client_id
- redirect_uri
- code_challenge SHA-256
- state CSRF token
- scope api]
E --> F[Open browser to authorization URL]
F --> G[User approves on GitLab]
G --> H[GitLab redirects to callback server]
H --> I[Callback server receives:
- code authorization code
- state for verification]
I --> J[Verify state matches]
J --> K[Exchange code for tokens:
POST /oauth/token with:
- code
- code_verifier
- redirect_uri]
K --> L[Receive tokens:
- access_token
- refresh_token
- expires_in]
L --> M[Save to auth.json with 600 permissions]
M --> N[Close callback server]
N --> O[Return success to OpenCode]
`#### PAT Authentication Flow
`mermaid
flowchart TD
A[User initiates /connect] --> B[Plugin prompts for:
- Instance URL
- Personal Access Token]
B --> C[Validate token format
starts with glpat-]
C --> D[Test token with API call:
GET /api/v4/user
Authorization: Bearer token]
D --> E{Successful?}
E -->|Yes| F[Save token to auth.json]
F --> G[Return success]
E -->|No| H[Return error]
H --> I[Prompt user to try again]
`$3
#### Token Storage
`
~/.local/share/opencode/auth.json
{
"gitlab": {
"type": "oauth",
"access": "ya29.a0AfH6SMBx...",
"refresh": "1//0gHZPQhYjIsN...",
"expires": 1735948800000,
"enterpriseUrl": "https://gitlab.com"
}
}
`Security Measures:
- File permissions:
600 (owner read/write only)
- JSON format for easy parsing
- Separate storage per provider
- Includes expiration timestamp
- Enterprise URL for multi-instance support#### PKCE Security
Traditional OAuth requires a client secret, which can't be securely stored in desktop applications. PKCE solves this:
Without PKCE (Insecure):
`mermaid
sequenceDiagram
participant Client
participant AuthServer as Authorization Server Client->>AuthServer: client_id + client_secret
Note over Client,AuthServer: Problem: Secret can be extracted from application
`With PKCE (Secure):
`mermaid
sequenceDiagram
participant Client
participant AuthServer as Authorization Server Note over Client: 1. Generate random verifier
Client->>AuthServer: 2. Send SHA-256(verifier) as challenge
Note over AuthServer: 3. Store challenge
Client->>AuthServer: 4. Send verifier to prove ownership
Note over AuthServer: 5. Verify SHA-256(verifier) == challenge
`Benefits:
- No client secret needed
- Verifier never transmitted during authorization
- Challenge can't be reversed to get verifier
- Protects against authorization code interception
#### State Parameter
Prevents CSRF attacks where an attacker tricks a user into authorizing their malicious app:
`mermaid
sequenceDiagram
participant Client
participant GitLab Note over Client: 1. Generate random state: "abc123xyz"
Note over Client: 2. Store state locally
Client->>GitLab: 3. Send state in authorization URL
GitLab->>Client: 4. Include state in redirect
Note over Client: 5. Verify returned state matches stored state
alt State mismatch
Note over Client: 6. Reject (possible attack)
else State matches
Note over Client: 6. Continue authentication
end
`#### Rate Limiting
Callback server includes rate limiting to prevent abuse:
`typescript
{
max: 30, // Maximum requests
timeWindow: 60000 // Per 60 seconds
}
`Protects against:
- Brute force attacks
- Denial of service
- Callback flooding
API Reference
$3
`typescript
import gitlabAuthPlugin from '@gitlab/opencode-gitlab-auth';// Default export
export default gitlabAuthPlugin;
// Named export
export const gitlabAuthPlugin: Plugin;
`$3
`typescript
// OAuth Flow Options
interface OAuthFlowOptions {
instanceUrl: string;
clientId: string;
scopes: string[];
method: 'auto' | 'code';
timeout?: number;
}// OAuth Tokens Response
interface OAuthTokens {
access_token: string;
refresh_token: string;
expires_in: number;
token_type: string;
scope: string;
created_at: number;
}
// Callback Server Options
interface CallbackServerOptions {
port?: number;
host?: string;
timeout?: number;
}
// Callback Result
interface CallbackResult {
code: string;
state: string;
}
`$3
The plugin implements OpenCode's
Plugin interface:`typescript
interface Plugin {
auth: AuthHook;
}interface AuthHook {
provider: string;
loader: (auth: () => Promise) => Promise;
methods: AuthMethod[];
}
`Security
$3
1. Use OAuth over PAT when possible for better security
2. Rotate PATs regularly if using token authentication
3. Never commit credentials or tokens to version control
4. Use environment variables for OAuth client IDs
5. Review authorized applications periodically in GitLab settings
6. Monitor auth logs for suspicious activity
$3
| Feature | Description | Benefit |
| --------------- | --------------------------- | ------------------------------------- |
| PKCE | Proof Key for Code Exchange | Eliminates need for client secrets |
| State Parameter | Random CSRF token | Prevents cross-site request forgery |
| Secure Storage | 600 file permissions | Protects credentials from other users |
| Token Refresh | Automatic renewal | Reduces exposure window |
| Rate Limiting | 30 req/min on callback | Prevents abuse |
| HTTPS Only | Enforced for GitLab API | Prevents man-in-the-middle attacks |
$3
Protected Against:
- ā
Authorization code interception (PKCE)
- ā
CSRF attacks (state parameter)
- ā
Token theft from filesystem (600 permissions)
- ā
Brute force attacks (rate limiting)
- ā
Man-in-the-middle (HTTPS enforcement)
Not Protected Against:
- ā ļø Malware with root/admin access
- ā ļø Physical access to unlocked machine
- ā ļø Compromised GitLab account
- ā ļø Social engineering attacks
$3
Please report security vulnerabilities to the maintainers privately. Do not open public issues for security concerns.
Troubleshooting
$3
#### OAuth: Browser doesn't open
Symptoms:
- Authorization URL displayed but browser doesn't open
- Manual URL copy required
Solutions:
1. Check if
open command is available:
`bash
# macOS
which open # Linux
which xdg-open
# Windows
where start
`2. Manually open the URL displayed in terminal
3. Check firewall settings for port 8080
#### OAuth: Callback timeout
Symptoms:
- "OAuth callback timeout" error after 2 minutes
- Browser shows success but OpenCode shows failure
Solutions:
1. Check if port 8080 is available:
`bash
lsof -i :8080 # macOS/Linux
netstat -ano | findstr :8080 # Windows
`2. Ensure no firewall blocking localhost:8080
3. Try increasing timeout in code (requires rebuild)
4. Check debug logs:
`bash
tail -f ~/.local/share/opencode/log/gitlab-auth.log
`#### OAuth: Invalid client
Symptoms:
- "The client identifier provided is invalid" error
- OAuth app not found
Solutions:
1. Verify
GITLAB_OAUTH_CLIENT_ID is set correctly:
`bash
echo $GITLAB_OAUTH_CLIENT_ID
`2. Check OAuth app settings on GitLab:
- Redirect URI must be:
http://127.0.0.1:8080/callback
- Application must be "Confidential"
- Scope must include "api"3. Ensure OAuth app is not expired or revoked
#### PAT: Authentication failed
Symptoms:
- "Authentication failed" when entering PAT
- Token validation fails
Solutions:
1. Verify token format:
- Must start with
glpat-
- No extra spaces or newlines2. Check token scopes:
- Must have
api scope
- Check in GitLab Settings > Access Tokens3. Verify token is not expired
4. Test token manually:
`bash
curl -H "Authorization: Bearer glpat-xxx" \
https://gitlab.com/api/v4/user
`#### Self-hosted GitLab issues
Symptoms:
- Connection errors with self-hosted instance
- SSL certificate errors
Solutions:
1. Verify instance URL format:
`
ā
https://gitlab.example.com
ā https://gitlab.example.com/
ā gitlab.example.com
`2. Check SSL certificate:
`bash
curl -v https://gitlab.example.com/api/v4/version
`3. For self-signed certificates (not recommended):
`bash
export NODE_TLS_REJECT_UNAUTHORIZED=0
`4. Ensure instance is accessible:
`bash
ping gitlab.example.com
`$3
Enable detailed logging by checking the log file:
`bash
View logs in real-time
tail -f ~/.local/share/opencode/log/gitlab-auth.logView last 50 lines
tail -n 50 ~/.local/share/opencode/log/gitlab-auth.logSearch for errors
grep -i error ~/.local/share/opencode/log/gitlab-auth.log
`Log entries include:
- Timestamp
- Event description
- Relevant data (sanitized)
- Error stack traces
$3
1. Check existing issues: GitLab Issues
2. Review debug logs:
~/.local/share/opencode/log/gitlab-auth.log
3. Open a new issue: Include logs and steps to reproduce
4. Community support: OpenCode Discord/SlackDevelopment
$3
`bash
Clone repository
git clone https://gitlab.com/gitlab-org/editor-extensions/opencode-gitlab-auth.git
cd opencode-gitlab-authInstall dependencies
npm installSet up Git hooks
npm run prepareBuild project
npm run build
`$3
| Script | Description |
| ---------------------- | -------------------------------- |
|
npm run build | Compile TypeScript to JavaScript |
| npm run clean | Remove dist/ directory |
| npm run rebuild | Clean and build |
| npm run lint | Check code style |
| 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 |$3
- TypeScript: Strict mode enabled
- Indentation: 2 spaces
- Quotes: Single quotes
- Semicolons: Required
- Line length: 100 characters max
- Naming:
- camelCase for variables/functions
- PascalCase for classes/interfaces
- UPPER_CASE for constants
$3
1. Create feature branch:
`bash
git checkout -b feat/your-feature
`2. Make changes and commit:
`bash
git add .
git commit -m "feat: add new feature"
`3. Push and create MR:
`bash
git push origin feat/your-feature
`$3
Follow Conventional Commits:
`
(): `Types:
-
feat: New feature
- fix: Bug fix
- docs: Documentation
- style: Code style (formatting)
- refactor: Code refactoring
- perf: Performance improvement
- test: Tests
- build: Build system
- ci: CI/CD
- chore: MaintenanceExamples:
`bash
feat(oauth): add support for custom redirect URIs
fix(server): resolve callback timeout issue
docs: update installation instructions
`$3
Currently, testing is manual. To test changes:
`bash
Build
npm run buildLink locally
npm linkTest with OpenCode
opencode
Use /connect command
`Test Checklist:
- [ ] OAuth flow completes successfully
- [ ] PAT authentication works
- [ ] Token refresh works (wait for expiration)
- [ ] Self-hosted GitLab instances work
- [ ] Error handling displays correctly
- [ ] Debug logs are written
- [ ] File permissions are correct (600)
$3
Releases are automated using semantic-release:
1. Commit changes following conventional commits
2. Push to
main branch
3. CI/CD pipeline runs:
- Lints code
- Builds project
- Determines version bump
- Updates CHANGELOG.md
- Creates Git tag
- Publishes to npmVersion bumps:
-
feat: ā Minor version (1.0.0 ā 1.1.0)
- fix: ā Patch version (1.0.0 ā 1.0.1)
- feat!: or BREAKING CHANGE: ā Major version (1.0.0 ā 2.0.0)Contributing
Contributions are welcome! Please see our Contributing Guide for detailed guidelines on:
- Code style and conventions
- Development workflow
- Testing requirements
- Submitting merge requests
- Developer Certificate of Origin and License
Quick Start for Contributors:
1. Commit Messages: Use conventional commits format
`
feat(scope): add new feature
fix(scope): fix bug
docs(scope): update documentation
`2. Code Quality: Ensure all checks pass
`bash
npm run lint
npm run format:check
npm run build
`3. Testing: Manual testing via
npm link and OpenCode integrationFAQ
$3
A: For best experience
---
Assistant
, yes. The bundled client ID has limitations. Register your own app at GitLab Applications.
$3
A: Yes! Enter your instance URL when prompted (e.g.,
https://gitlab.company.com).$3
A:
- OAuth: Go to GitLab Settings > Applications > Authorized Applications
- PAT: Go to GitLab Settings > Access Tokens > Revoke
$3
A: In
~/.local/share/opencode/auth.json` with 600 permissions (owner only).A: Currently, only one account per instance is supported. You'll need to re-authenticate to switch accounts.
A: No, authentication requires internet access to GitLab. However, once authenticated, OpenCode may cache some data.
A: Tokens are stored with 600 file permissions and never transmitted except to GitLab's API over HTTPS. However, any process running as your user can access them.
A: Yes, use Personal Access Token authentication. OAuth requires browser interaction.
A: OAuth access tokens expire after 2 hours but are automatically refreshed. PATs last until their configured expiration date.
See CHANGELOG.md for version history and release notes.
MIT License - see LICENSE file for details.
Copyright (c) 2025 OpenCode GitLab Auth Contributors
- Inspired by gitlab-vscode-extension
- OAuth patterns from gitlab-lsp
- Built for OpenCode
- GitLab Repository
- npm Package
- Issue Tracker
- Contributing Guide
- Changelog
- Agent Guidelines
This project is built for:
- OpenCode
- GitLab Duo
- GitLab OAuth
---
Made with ā¤ļø for the OpenCode community
---