Skip to main content

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 ID
  • redirect_uri: Callback URL (must match registered URI)
  • response_type: Always code for authorization code flow
  • scope: Requested permissions (space-separated)
  • state: CSRF protection token
  • code_challenge: PKCE code challenge
  • code_challenge_method: PKCE method (S256 for 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

ScopeDescriptionPermission Level
read_userRead user profileUser info only
read_apiRead GitLab APIRead-only access
write_apiWrite GitLab APIFull API access
read_repositoryRead repositoriesClone, pull
write_repositoryWrite repositoriesPush, create
sudoAdmin operationsAdmin only
apiFull API accessAll operations

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/callback
  • http://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

Next Steps