OAuth 2.0 with GitLab
OAuth 2.0 with GitLab
Separation of Duties: See Separation of Duties - API documentation is responsible for documenting APIs. It does NOT own agent manifests, execution, or infrastructure configuration.
OAuth 2.0 authentication using GitLab as identity provider.
Overview
The platform uses GitLab OAuth 2.0 for user authentication:
- GitLab as IdP: Single sign-on via GitLab.com or self-hosted GitLab
- Authorization Code Flow: Standard OAuth 2.0 flow with PKCE
- Group-based RBAC: Automatic role assignment based on GitLab groups
- Token exchange: Exchange GitLab token for platform JWT
- API integration: Access GitLab API on behalf of users
Architecture
sequenceDiagram participant User participant App participant Auth Service participant GitLab User->>App: Click "Login with GitLab" App->>Auth Service: Initiate OAuth Auth Service->>GitLab: Authorization Request GitLab->>User: Login Page User->>GitLab: Authenticate GitLab->>Auth Service: Authorization Code Auth Service->>GitLab: Exchange Code for Token GitLab->>Auth Service: GitLab Access Token Auth Service->>App: Platform JWT App->>User: Authenticated
OAuth Flow
1. Initiate Authorization
GET /api/v1/auth/oauth/authorize?provider=gitlab
Redirect to GitLab:
https://gitlab.com/oauth/authorize
?client_id=abc123
&redirect_uri=https://llm.bluefly.io/auth/callback
&response_type=code
&scope=read_user+read_api+read_repository
&state=random-state-string
&code_challenge=challenge-string
&code_challenge_method=S256
Query Parameters:
client_id: OAuth app client IDredirect_uri: Callback URL (must match registered URI)response_type: Alwayscodefor authorization code flowscope: Requested permissions (space-separated)state: CSRF protection tokencode_challenge: PKCE code challengecode_challenge_method: PKCE method (S256for SHA-256)
2. User Authorizes
User logs into GitLab and authorizes the application.
3. Authorization Callback
GitLab redirects back with authorization code:
https://llm.bluefly.io/auth/callback
?code=authorization-code-abc123
&state=random-state-string
4. Exchange Code for Token
POST /api/v1/auth/oauth/token Content-Type: application/json
Request:
{ "grant_type": "authorization_code", "code": "authorization-code-abc123", "redirect_uri": "https://llm.bluefly.io/auth/callback", "client_id": "abc123", "code_verifier": "verifier-string" }
Response:
{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "expires_in": 3600, "scope": "read write admin", "user": { "id": "user-123", "username": "developer", "email": "developer@bluefly.io", "name": "John Developer", "avatar_url": "https://gitlab.com/uploads/...", "gitlab_id": 456, "roles": ["developer", "agent-admin"], "groups": [ { "id": 789, "name": "llm-platform", "role": "maintainer" } ] } }
OAuth Scopes
Available Scopes
| Scope | Description | Permission Level |
|---|---|---|
read_user | Read user profile | User info only |
read_api | Read GitLab API | Read-only access |
write_api | Write GitLab API | Full API access |
read_repository | Read repositories | Clone, pull |
write_repository | Write repositories | Push, create |
sudo | Admin operations | Admin only |
api | Full API access | All operations |
Recommended Scopes
Read-only User:
read_user read_api
Developer:
read_user read_api read_repository write_repository
Admin:
read_user api
Group-Based RBAC
Roles are automatically assigned based on GitLab group membership:
Group to Role Mapping
{ "groupMappings": [ { "gitlabGroup": "llm-platform/admins", "platformRole": "admin", "permissions": ["*"] }, { "gitlabGroup": "llm-platform/developers", "platformRole": "developer", "permissions": [ "agent:execute", "workflow:create", "mesh:communicate" ] }, { "gitlabGroup": "llm-platform/users", "platformRole": "user", "permissions": [ "agent:read", "workflow:read" ] } ] }
Role Inheritance
Users inherit permissions from all groups they belong to:
Example:
{ "user": "developer@bluefly.io", "groups": [ "llm-platform/developers", "llm-platform/ml-team" ], "roles": ["developer", "ml-engineer"], "permissions": [ "agent:execute", "workflow:create", "mesh:communicate", "model:train", "model:deploy" ] }
GitLab API Access
Access GitLab API on behalf of authenticated user:
Get User's GitLab Token
GET /api/v1/auth/oauth/gitlab-token Authorization: Bearer eyJhbGci...
Response:
{ "access_token": "gitlab-token-xyz789", "token_type": "Bearer", "expires_in": 7200, "refresh_token": "gitlab-refresh-abc123", "created_at": 1705320000, "scope": "read_user read_api" }
Use GitLab Token
curl -H "Authorization: Bearer gitlab-token-xyz789" \ https://gitlab.com/api/v4/user
Response:
{ "id": 456, "username": "developer", "email": "developer@bluefly.io", "name": "John Developer", "state": "active", "avatar_url": "https://gitlab.com/uploads/...", "web_url": "https://gitlab.com/developer" }
Token Refresh
Refresh expired GitLab token:
POST /api/v1/auth/oauth/refresh Content-Type: application/json
Request:
{ "grant_type": "refresh_token", "refresh_token": "gitlab-refresh-abc123", "provider": "gitlab" }
Response:
{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "expires_in": 3600, "gitlab_token": { "access_token": "gitlab-token-new123", "refresh_token": "gitlab-refresh-new456", "expires_in": 7200 } }
PKCE (Proof Key for Code Exchange)
PKCE prevents authorization code interception attacks:
1. Generate Code Verifier
JavaScript:
function generateCodeVerifier() { const array = new Uint8Array(32); crypto.getRandomValues(array); return base64URLEncode(array); }
2. Generate Code Challenge
async function generateCodeChallenge(verifier) { const encoder = new TextEncoder(); const data = encoder.encode(verifier); const hash = await crypto.subtle.digest('SHA-256', data); return base64URLEncode(new Uint8Array(hash)); }
3. Store Code Verifier
Store verifier in session storage for callback:
sessionStorage.setItem('code_verifier', codeVerifier);
4. Send Challenge in Authorization
https://gitlab.com/oauth/authorize
?code_challenge=BASE64_ENCODED_CHALLENGE
&code_challenge_method=S256
...
5. Send Verifier in Token Exchange
{ "code_verifier": "original-code-verifier", ... }
Self-Hosted GitLab
Configure custom GitLab instance:
Update OAuth Configuration
POST /api/v1/auth/oauth/configure Authorization: Bearer admin-token Content-Type: application/json
Request:
{ "provider": "gitlab-custom", "config": { "authorizationURL": "https://gitlab.yourcompany.com/oauth/authorize", "tokenURL": "https://gitlab.yourcompany.com/oauth/token", "userInfoURL": "https://gitlab.yourcompany.com/api/v4/user", "clientID": "your-client-id", "clientSecret": "your-client-secret", "callbackURL": "https://llm.bluefly.io/auth/callback/gitlab-custom" } }
Security Considerations
State Parameter
Always validate state parameter to prevent CSRF:
const state = generateRandomString(); sessionStorage.setItem('oauth_state', state); // Later, in callback: const returnedState = new URLSearchParams(window.location.search).get('state'); if (returnedState !== sessionStorage.getItem('oauth_state')) { throw new Error('Invalid state parameter - possible CSRF attack'); }
Redirect URI Validation
Only allow whitelisted redirect URIs:
Allowed:
https://llm.bluefly.io/auth/callbackhttp://localhost:3000/auth/callback(development only)
Blocked:
https://evil.com/steal-token
Token Storage
Store tokens securely:
Recommended:
- HTTP-only secure cookies
- Server-side session storage
- Encrypted browser storage
Avoid:
- LocalStorage (XSS vulnerable)
- URL parameters
- Plain cookies
Integration Examples
React Application
import { useAuth } from '@bluefly/auth-react'; function LoginButton() { const { loginWithGitLab } = useAuth(); const handleLogin = async () => { const user = await loginWithGitLab({ scope: 'read_user read_api', redirectUri: window.location.origin + '/auth/callback' }); console.log('Logged in as:', user.name); }; return <button onClick={handleLogin}>Login with GitLab</button>; }
Node.js Backend
import { OAuthClient } from '@bluefly/auth-server'; const oauth = new OAuthClient({ provider: 'gitlab', clientId: process.env.GITLAB_CLIENT_ID, clientSecret: process.env.GITLAB_CLIENT_SECRET, redirectUri: 'https://llm.bluefly.io/auth/callback' }); app.get('/auth/gitlab', (req, res) => { const authUrl = oauth.getAuthorizationUrl({ scope: 'read_user read_api', state: req.session.state }); res.redirect(authUrl); }); app.get('/auth/callback', async (req, res) => { const { code, state } = req.query; if (state !== req.session.state) { return res.status(403).send('Invalid state'); } const tokens = await oauth.exchangeCodeForToken({ code, codeVerifier: req.session.codeVerifier }); req.session.user = tokens.user; res.redirect('/dashboard'); });
Troubleshooting
Common Errors
Error: invalid_grant
- Code expired (10 minutes max)
- Code already used
- Code verifier mismatch
Error: redirect_uri_mismatch
- Callback URL doesn't match registered URI
- Protocol mismatch (http vs https)
- Port mismatch
Error: access_denied
- User denied authorization
- User lacks required GitLab permissions
Debug Mode
Enable OAuth debug logging:
export OAUTH_DEBUG=true export OAUTH_LOG_LEVEL=debug
Logs will include:
- Authorization URLs
- Token exchange requests
- User profile data
- Group memberships
- Role assignments