OpenCode GitLab Auth
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.

Features
š Dual Authentication Methods
- 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
š”ļø Security First
- 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)
š Flexible Instance Support
- GitLab.com (default)
- Self-hosted GitLab instances
- Custom instance URL configuration
š Developer Experience
- 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
Table of Contents
Installation
Prerequisites
- Node.js 18.0.0 or higher
- npm 9.0.0 or higher
- OpenCode installed and configured
Install via npm
npm install @gitlab-org/opencode-gitlab-auth
Install via npm link (Development)
git clone https://gitlab.com/vglafirov/opencode-gitlab-auth.git
cd opencode-gitlab-auth
npm install
npm run build
npm link
npm link @gitlab-org/opencode-gitlab-auth
Quick Start
1. Configure OpenCode
Add the plugin to your OpenCode configuration:
{
"plugins": ["@gitlab-org/opencode-gitlab-auth"]
}
2. Set Up OAuth (Optional but Recommended)
For OAuth authentication, you need to register an OAuth application on GitLab:
- Go to GitLab User Settings > Applications
- Create a new application with:
- Name:
OpenCode GitLab Auth
- Redirect URI:
http://127.0.0.1:8080/callback
- Confidential: ā
(checked)
- Scopes:
api
- Copy the Application ID (NOT the secret - PKCE doesn't need it)
- Set the environment variable:
export GITLAB_OAUTH_CLIENT_ID=your_application_id_here
Or create a .env file:
GITLAB_OAUTH_CLIENT_ID=your_application_id_here
3. Authenticate
Start OpenCode and use the /connect command:
opencode
/connect
Choose your authentication method:
- GitLab OAuth - Browser-based authentication (recommended)
- GitLab Personal Access Token - Token-based authentication
Authentication Methods
OAuth 2.0 (Recommended)
OAuth provides the most secure authentication with automatic token refresh and no need to manage long-lived credentials.
How It Works
- Authorization Request: OpenCode opens your browser to GitLab's authorization page
- User Consent: You approve the application's access request
- Callback Handling: GitLab redirects to a local callback server
- Token Exchange: The authorization code is exchanged for access and refresh tokens using PKCE
- Secure Storage: Tokens are stored in
~/.local/share/opencode/auth.json with 600 permissions
OAuth Flow Diagram
sequenceDiagram
participant OpenCode
participant Browser
participant GitLab
participant LocalServer as Local Callback Server
participant Storage as Secure Storage
OpenCode->>Browser: 1. Open Authorization URL<br/>(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<br/>(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
Personal Access Token
PAT authentication is simpler but requires manual token management.
How to Create a PAT
- Go to GitLab User Settings > Access Tokens
- Create a new token with:
- Name:
OpenCode
- Scopes:
api
- Expiration: Set according to your security policy
- Copy the token (starts with
glpat-)
- 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
Environment Variables
GITLAB_OAUTH_CLIENT_ID | OAuth application ID | For OAuth | Bundled ID (limited) |
XDG_DATA_HOME | Custom data directory | No | ~/.local/share |
Storage Locations
Credentials are stored in platform-specific locations:
- Linux/macOS:
~/.local/share/opencode/auth.json
- Windows:
~/.opencode/auth.json
- Custom:
$XDG_DATA_HOME/opencode/auth.json
File permissions are automatically set to 600 (owner read/write only).
Debug Logging
Debug logs are written to:
- Linux/macOS:
~/.local/share/opencode/log/gitlab-auth.log
- Windows:
~/.opencode/log/gitlab-auth.log
Logs include:
- Authentication flow steps
- Token exchange details
- Error messages and stack traces
- Timestamps for all events
Architecture
Project Structure
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
Core Components
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 permissions
Exports:
export const gitlabAuthPlugin: Plugin;
export default gitlabAuthPlugin;
Authentication Hook:
interface AuthHook {
provider: 'gitlab';
loader: (auth: () => Promise<AuthData>) => Promise<LoaderResult>;
methods: [OAuthMethod, PATMethod];
}
2. OAuth Flow (oauth-flow.ts)
Implements the complete OAuth 2.0 authorization code flow with PKCE.
Class: GitLabOAuthFlow
class GitLabOAuthFlow {
constructor(options: OAuthFlowOptions);
async authorize(): Promise<AuthorizationResult>;
async exchangeAuthorizationCode(
code: string,
codeVerifier: string,
redirectUri: string
): Promise<OAuthTokens>;
async exchangeRefreshToken(refreshToken: string): Promise<OAuthTokens>;
}
Interfaces:
interface OAuthFlowOptions {
instanceUrl: string;
clientId: string;
scopes: string[];
method: 'auto' | 'code';
timeout?: number;
}
interface OAuthTokens {
access_token: string;
refresh_token: string;
expires_in: number;
token_type: string;
scope: string;
created_at: number;
}
interface AuthorizationResult {
code: string;
state: string;
codeVerifier: string;
}
Flow Steps:
- Generate PKCE parameters (verifier & challenge)
- Generate random state for CSRF protection
- Build authorization URL with parameters
- Open browser or provide manual URL
- Wait for callback with authorization code
- Verify state parameter matches
- Exchange code for tokens using PKCE verifier
- Return tokens for storage
3. Callback Server (callback-server.ts)
Local HTTP server using Fastify to handle OAuth redirects.
Class: CallbackServer
class CallbackServer {
constructor(options?: CallbackServerOptions);
async start(): Promise<void>;
async waitForCallback(): Promise<CallbackResult>;
getPort(): number;
getCallbackUrl(): string;
async close(): Promise<void>;
}
Interfaces:
interface CallbackServerOptions {
port?: number;
host?: string;
timeout?: number;
}
interface CallbackResult {
code: string;
state: string;
}
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:
function generateSecret(length: number = 43): string;
function generateCodeChallengeFromVerifier(verifier: string): string;
function base64UrlEncode(buffer: Buffer): string;
PKCE Flow:
-
Code Verifier: Random 43-character string
const verifier = generateSecret(43);
-
Code Challenge: SHA-256 hash of verifier
const challenge = generateCodeChallengeFromVerifier(verifier);
-
Authorization: Send challenge to GitLab
-
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
Component Interactions
graph TB
OpenCode[OpenCode Core]
Plugin[index.ts Plugin]
AuthHook[AuthHook<br/>- loader<br/>- methods OAuth & PAT]
OAuthFlow[oauth-flow.ts<br/>GitLabOAuthFlow]
PATFlow[Direct API Call<br/>/api/v4/user]
CallbackServer[callback-server.ts<br/>CallbackServer Fastify HTTP]
PKCE[pkce.ts<br/>PKCE Utilities Crypto functions]
OpenCode -->|Plugin API| Plugin
Plugin --> AuthHook
AuthHook -->|OAuth Flow| OAuthFlow
AuthHook -->|PAT Flow| PATFlow
OAuthFlow -->|Uses| CallbackServer
OAuthFlow -->|Uses| PKCE
Data Flow
OAuth Authentication Flow
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:<br/>- client_id<br/>- redirect_uri<br/>- code_challenge SHA-256<br/>- state CSRF token<br/>- 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:<br/>- code authorization code<br/>- state for verification]
I --> J[Verify state matches]
J --> K[Exchange code for tokens:<br/>POST /oauth/token with:<br/>- code<br/>- code_verifier<br/>- redirect_uri]
K --> L[Receive tokens:<br/>- access_token<br/>- refresh_token<br/>- 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
flowchart TD
A[User initiates /connect] --> B[Plugin prompts for:<br/>- Instance URL<br/>- Personal Access Token]
B --> C[Validate token format<br/>starts with glpat-]
C --> D[Test token with API call:<br/>GET /api/v4/user<br/>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]
Security Architecture
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):
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):
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:
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:
{
max: 30,
timeWindow: 60000
}
Protects against:
- Brute force attacks
- Denial of service
- Callback flooding
API Reference
Plugin Export
import gitlabAuthPlugin from '@gitlab-org/opencode-gitlab-auth';
export default gitlabAuthPlugin;
export const gitlabAuthPlugin: Plugin;
TypeScript Types
interface OAuthFlowOptions {
instanceUrl: string;
clientId: string;
scopes: string[];
method: 'auto' | 'code';
timeout?: number;
}
interface OAuthTokens {
access_token: string;
refresh_token: string;
expires_in: number;
token_type: string;
scope: string;
created_at: number;
}
interface CallbackServerOptions {
port?: number;
host?: string;
timeout?: number;
}
interface CallbackResult {
code: string;
state: string;
}
OpenCode Integration
The plugin implements OpenCode's Plugin interface:
interface Plugin {
auth: AuthHook;
}
interface AuthHook {
provider: string;
loader: (auth: () => Promise<AuthData>) => Promise<LoaderResult>;
methods: AuthMethod[];
}
Security
Best Practices
- Use OAuth over PAT when possible for better security
- Rotate PATs regularly if using token authentication
- Never commit credentials or tokens to version control
- Use environment variables for OAuth client IDs
- Review authorized applications periodically in GitLab settings
- Monitor auth logs for suspicious activity
Security Features
| 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 |
Threat Model
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
Reporting Security Issues
Please report security vulnerabilities to the maintainers privately. Do not open public issues for security concerns.
Troubleshooting
Common Issues
OAuth: Browser doesn't open
Symptoms:
- Authorization URL displayed but browser doesn't open
- Manual URL copy required
Solutions:
-
Check if open command is available:
which open
which xdg-open
where start
-
Manually open the URL displayed in terminal
-
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:
-
Check if port 8080 is available:
lsof -i :8080
netstat -ano | findstr :8080
-
Ensure no firewall blocking localhost:8080
-
Try increasing timeout in code (requires rebuild)
-
Check debug logs:
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:
-
Verify GITLAB_OAUTH_CLIENT_ID is set correctly:
echo $GITLAB_OAUTH_CLIENT_ID
-
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"
-
Ensure OAuth app is not expired or revoked
PAT: Authentication failed
Symptoms:
- "Authentication failed" when entering PAT
- Token validation fails
Solutions:
Self-hosted GitLab issues
Symptoms:
- Connection errors with self-hosted instance
- SSL certificate errors
Solutions:
-
Verify instance URL format:
ā
https://gitlab.example.com
ā https://gitlab.example.com/
ā gitlab.example.com
-
Check SSL certificate:
curl -v https://gitlab.example.com/api/v4/version
-
For self-signed certificates (not recommended):
export NODE_TLS_REJECT_UNAUTHORIZED=0
-
Ensure instance is accessible:
ping gitlab.example.com
Debug Logging
Enable detailed logging by checking the log file:
tail -f ~/.local/share/opencode/log/gitlab-auth.log
tail -n 50 ~/.local/share/opencode/log/gitlab-auth.log
grep -i error ~/.local/share/opencode/log/gitlab-auth.log
Log entries include:
- Timestamp
- Event description
- Relevant data (sanitized)
- Error stack traces
Getting Help
- Check existing issues: GitLab Issues
- Review debug logs:
~/.local/share/opencode/log/gitlab-auth.log
- Open a new issue: Include logs and steps to reproduce
- Community support: OpenCode Discord/Slack
Development
Setup Development Environment
git clone https://gitlab.com/vglafirov/opencode-gitlab-auth.git
cd opencode-gitlab-auth
npm install
npm run prepare
npm run build
Available Scripts
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 |
Code Style
- 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
Git Workflow
-
Create feature branch:
git checkout -b feat/your-feature
-
Make changes and commit:
git add .
git commit -m "feat: add new feature"
-
Push and create MR:
git push origin feat/your-feature
Commit Message Format
Follow Conventional Commits:
<type>(<scope>): <subject>
<body>
<footer>
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: Maintenance
Examples:
feat(oauth): add support for custom redirect URIs
fix(server): resolve callback timeout issue
docs: update installation instructions
Testing
Currently, testing is manual. To test changes:
npm run build
npm link
opencode
Test Checklist:
Release Process
Releases are automated using semantic-release:
- Commit changes following conventional commits
- Push to
main branch
- CI/CD pipeline runs:
- Lints code
- Builds project
- Determines version bump
- Updates CHANGELOG.md
- Creates Git tag
- Publishes to npm
Version 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
We welcome contributions! Please see CONTRIBUTING.md for detailed guidelines.
Quick Contribution Guide
- Fork the repository
- Create a feature branch
- Make your changes
- Write/update tests (when available)
- Update documentation
- Submit a merge request
Code of Conduct
- Be respectful and constructive
- Follow the code style guidelines
- Write clear commit messages
- Document your changes
- Help others in discussions
FAQ
Q: Do I need to register an OAuth app?
A: For best experience
Assistant
, yes. The bundled client ID has limitations. Register your own app at GitLab Applications.
Q: Can I use this with GitLab self-hosted?
A: Yes! Enter your instance URL when prompted (e.g., https://gitlab.company.com).
Q: How do I revoke access?
A:
- OAuth: Go to GitLab Settings > Applications > Authorized Applications
- PAT: Go to GitLab Settings > Access Tokens > Revoke
Q: Where are my credentials stored?
A: In ~/.local/share/opencode/auth.json with 600 permissions (owner only).
Q: Can I use multiple GitLab accounts?
A: Currently, only one account per instance is supported. You'll need to re-authenticate to switch accounts.
Q: Does this work offline?
A: No, authentication requires internet access to GitLab. However, once authenticated, OpenCode may cache some data.
Q: Is my token secure?
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.
Q: Can I use this in CI/CD?
A: Yes, use Personal Access Token authentication. OAuth requires browser interaction.
Q: How long do tokens last?
A: OAuth access tokens expire after 2 hours but are automatically refreshed. PATs last until their configured expiration date.
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
Links
Made with ā¤ļø for the OpenCode community