← Documentation Home

OAuth 2.0 with GitLab

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.bluefly.io/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.bluefly.io/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

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.bluefly.io/api/v4/user

Response:

{
  "id": 456,
  "username": "developer",
  "email": "developer@bluefly.io",
  "name": "John Developer",
  "state": "active",
  "avatar_url": "https://gitlab.bluefly.io/uploads/...",
  "web_url": "https://gitlab.bluefly.io/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.bluefly.io/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