OAuth2/OIDC and LDAP/AD Integration

Configuration Guide for IT and Security Teams


Table of Contents

Overview

CodeGraph supports enterprise Single Sign-On (SSO) via OAuth2/OIDC and LDAP/Active Directory integration. Both methods are initialized at application startup and produce SIEM-auditable security events on every authentication attempt.

Key Capabilities

Feature Description
6 OAuth2 providers GitHub, Google, Keycloak (OIDC), GitLab, SourceCraft (Yandex ID), GitVerse (Sber ID)
LDAP/AD support Active Directory, OpenLDAP with group sync
Auto-provisioning Users created on first login from SSO/LDAP
RBAC mapping LDAP groups map to CodeGraph roles (ADMIN, REVIEWER, ANALYST, VIEWER)
CSRF protection State parameter with server-side validation
SIEM auditing AUTH_SUCCESS / AUTH_FAILURE events on every login
Graceful degradation App starts even if OAuth/LDAP is unavailable

Architecture

Authentication Flow

                     ┌──────────────┐
                     │   Browser /  │
                     │   API Client │
                     └──────┬───────┘
                            │
              ┌─────────────┼─────────────┐
              │             │             │
       ┌──────▼──────┐ ┌───▼────┐ ┌──────▼──────┐
       │  OAuth2     │ │  LDAP  │ │  Local JWT  │
       │  /OIDC      │ │  /AD   │ │  + API Key  │
       └──────┬──────┘ └───┬────┘ └──────┬──────┘
              │             │             │
              └─────────────┼─────────────┘
                            │
                    ┌───────▼───────┐
                    │  User Repo    │
                    │  (find or     │
                    │   create)     │
                    └───────┬───────┘
                            │
                    ┌───────▼───────┐       ┌──────────────┐
                    │  JWT Tokens   │       │  SIEM Event  │
                    │  (access +    │──────►│  Dispatch    │
                    │   refresh)    │       │  (auth.*)    │
                    └───────────────┘       └──────────────┘

Provider Registration

On startup, the application lifespan() initializes authentication subsystems:

  1. OAuthsetup_oauth_providers() reads OAUTH_* environment variables, registers enabled providers with OAuthManager
  2. IAMsetup_iam_validator() initializes Yandex Cloud IAM token validation (if IAM_ENABLED=true)
  3. LDAPsetup_ldap_authenticator() reads LDAP_* environment variables, creates the singleton LDAPAuthenticator

All blocks are wrapped in try/except — the application starts normally even if no SSO is configured.


OAuth2/OIDC

Supported Providers

CodeGraph supports 6 OAuth2/OIDC providers. The 4 base providers are listed in the /oauth/providers endpoint; SourceCraft and GitVerse are platform-specific integrations with their own dedicated documentation.

Provider Authorize URL Token URL Scopes
GitHub github.com/login/oauth/authorize github.com/login/oauth/access_token user:email
Google accounts.google.com/o/oauth2/v2/auth oauth2.googleapis.com/token openid, email, profile
Keycloak {server}/realms/{realm}/protocol/openid-connect/auth .../token openid, email, profile
GitLab gitlab.com/oauth/authorize (or self-hosted) gitlab.com/oauth/token read_user, openid
SourceCraft {server}/authorize (Yandex ID) {server}/token login:email, login:info
GitVerse {server}/oauth/authorize (Sber ID) {server}/oauth/token read:user, user:email

Note: SourceCraft and GitVerse are documented in detail in SourceCraft Integration and GitVerse Integration.

Configuration

Set environment variables for each provider you want to enable:

# GitHub
export OAUTH_GITHUB_CLIENT_ID="your_github_client_id"
export OAUTH_GITHUB_CLIENT_SECRET="your_github_client_secret"

# Google
export OAUTH_GOOGLE_CLIENT_ID="your_google_client_id"
export OAUTH_GOOGLE_CLIENT_SECRET="your_google_client_secret"

# Keycloak (OIDC)
export OAUTH_KEYCLOAK_SERVER_URL="https://keycloak.corp.example.com"
export OAUTH_KEYCLOAK_REALM="codegraph"
export OAUTH_KEYCLOAK_CLIENT_ID="codegraph-app"
export OAUTH_KEYCLOAK_CLIENT_SECRET="your_keycloak_client_secret"

# SourceCraft (Yandex ID)
export OAUTH_SOURCECRAFT_CLIENT_ID="your_sourcecraft_client_id"
export OAUTH_SOURCECRAFT_CLIENT_SECRET="your_sourcecraft_client_secret"
export OAUTH_SOURCECRAFT_SERVER_URL="https://oauth.yandex.ru"    # default

# GitVerse (Sber ID)
export OAUTH_GITVERSE_CLIENT_ID="your_gitverse_client_id"
export OAUTH_GITVERSE_CLIENT_SECRET="your_gitverse_client_secret"
export OAUTH_GITVERSE_SERVER_URL="https://gitverse.ru"           # default

Providers without CLIENT_ID and CLIENT_SECRET are skipped automatically.

Note: GitLab OAuth is supported in the codebase but does not have dedicated OAUTH_GITLAB_* environment variables. To enable GitLab, configure it directly in config.yaml under auth.oauth.gitlab with client_id, client_secret, and optionally authorize_url / token_url for self-hosted instances.

Authorization Flow

1. User    GET /api/v1/auth/oauth/{provider}
2. Server generates CSRF state, stores in memory
3. 302 redirect to provider's authorize URL
4. User authenticates at provider
5. Provider redirects to GET /api/v1/auth/oauth/{provider}/callback?code=...&state=...
6. Server verifies state (CSRF), exchanges code for access token
7. Server fetches user info from provider API
8. Server finds or creates user in database
9. Server returns JWT tokens (access + refresh)

User Provisioning

On first OAuth login, a new user is created automatically:

Field Source
username Provider-specific (GitHub: login, Google: email prefix, Keycloak: preferred_username)
email Provider user info
auth_provider Per-provider: oauth_github, oauth_google, oauth_gitlab, oauth_keycloak
role Default: ANALYST
external_id Provider’s unique user ID

Note: The display_name field from the provider’s user info is passed to the provisioning method but is not currently persisted in the database. The username field serves as the primary display identifier.

Subsequent logins match by (auth_provider, external_id) and reuse the existing account.


LDAP/Active Directory

LDAP Configuration

# Required
export LDAP_SERVER="ldaps://ldap.corp.example.com"
export LDAP_BASE_DN="dc=corp,dc=example,dc=com"

# Service account for searches
export LDAP_BIND_USER="cn=codegraph-svc,ou=service-accounts,dc=corp,dc=example,dc=com"
export LDAP_BIND_PASSWORD="service_account_password"

# Search bases
export LDAP_USER_SEARCH_BASE="ou=users,dc=corp,dc=example,dc=com"
export LDAP_GROUP_SEARCH_BASE="ou=groups,dc=corp,dc=example,dc=com"

Full LDAPConfig in config.yaml:

auth:
  ldap:
    enabled: true
    server: "ldaps://ldap.corp.example.com"
    port: 636
    use_ssl: true
    base_dn: "dc=corp,dc=example,dc=com"
    user_search_base: "ou=users,dc=corp,dc=example,dc=com"
    group_search_base: "ou=groups,dc=corp,dc=example,dc=com"
    bind_user: "cn=codegraph-svc,ou=service-accounts,dc=corp,dc=example,dc=com"
    bind_password: "${LDAP_BIND_PASSWORD}"
    user_object_class: "person"
    group_object_class: "group"
    username_attribute: "sAMAccountName"
    email_attribute: "mail"
    group_membership_attribute: "memberOf"
    role_mapping:
      "cn=codegraph-admins,ou=groups,dc=corp,dc=example,dc=com": "admin"
      "cn=codegraph-reviewers,ou=groups,dc=corp,dc=example,dc=com": "reviewer"
      "cn=codegraph-analysts,ou=groups,dc=corp,dc=example,dc=com": "analyst"

Group-to-Role Mapping

LDAP groups map to CodeGraph RBAC roles via role_mapping in LDAPConfig. The LDAPAuthenticator checks the user’s groups in priority order (admin > reviewer > analyst > viewer). First match wins.

LDAP Group CodeGraph Role Permissions
codegraph-admins ADMIN Full access (admin:all)
codegraph-reviewers REVIEWER Code review + analyst permissions
codegraph-analysts ANALYST Query execution + API keys
(no match) ANALYST Default fallback

Known issue: The LDAPAuthenticator.map_groups_to_role() method accesses config.group_role_mapping instead of the role_mapping field defined in LDAPConfig. This causes the mapping to silently fall through to the default analyst role. The fix requires aligning the attribute name in ldap_auth.py with the LDAPConfig field name.

LDAP User Provisioning

On first LDAP login, a user is created via LDAPAuthenticator:

Field Source
username LDAP sAMAccountName (or configured attribute)
email LDAP mail attribute
ldap_dn Full distinguished name (stored as external_id)
auth_provider ldap
role From group-to-role mapping

Note: The display_name from LDAP displayName attribute is passed to create_ldap_user() but is not currently persisted in the database.

Subsequent logins match by ldap_dn (stored as external_id with auth_provider=ldap).


SIEM Integration

All authentication attempts (success and failure) generate SIEM events dispatched to configured handlers (Syslog, CEF, LEEF).

Authentication Events

Event Type Severity Trigger
AUTH_SUCCESS 6 (Informational) Successful login via any method
AUTH_FAILURE 4 (Warning) Failed login — wrong credentials, unknown user, provider error

Event Examples

Successful OAuth login:

{
  "event_type": "auth.success",
  "timestamp": "2026-02-26T10:30:00.000Z",
  "severity": 6,
  "message": "OAuth login: ghuser via github",
  "user_id": "550e8400-e29b-41d4-a716-446655440000",
  "ip_address": "10.0.0.50",
  "provider": "github",
  "request_id": "req-abc123"
}

Failed LDAP login:

{
  "event_type": "auth.failure",
  "timestamp": "2026-02-26T10:31:00.000Z",
  "severity": 4,
  "message": "LDAP auth failed: unknown_user",
  "ip_address": "10.0.0.51",
  "request_id": "req-def456"
}

Failed local login (wrong password):

{
  "event_type": "auth.failure",
  "timestamp": "2026-02-26T10:32:00.000Z",
  "severity": 4,
  "message": "Login failed: invalid password for analyst01",
  "user_id": "550e8400-e29b-41d4-a716-446655440001",
  "ip_address": "10.0.0.52",
  "request_id": "req-ghi789"
}

API Reference

OAuth Endpoints

Method Path Description
GET /api/v1/auth/oauth/providers List 4 base OAuth providers and their status
GET /api/v1/auth/oauth/{provider} Start OAuth flow (redirects to provider)
GET /api/v1/auth/oauth/{provider}/callback Handle OAuth callback, return JWT tokens

List providers response:

Note: The /oauth/providers endpoint lists the 4 base providers (github, google, gitlab, keycloak) with a hardcoded list. SourceCraft and GitVerse are registered in OAuthManager and accept OAuth flows via /oauth/{provider}, but are not included in the providers listing.

[
  {"name": "github", "enabled": true, "authorize_url": "https://github.com/login/oauth/authorize?..."},
  {"name": "google", "enabled": false, "authorize_url": null},
  {"name": "gitlab", "enabled": false, "authorize_url": null},
  {"name": "keycloak", "enabled": false, "authorize_url": null}
]

Callback success response:

{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "bearer",
  "expires_in": 1800
}

LDAP Endpoints

Method Path Description
POST /api/v1/auth/ldap Authenticate with LDAP credentials
GET /api/v1/auth/ldap/status Check LDAP connection status

LDAP login request:

{
  "username": "jdoe",
  "password": "corporate_password"
}

LDAP status response (connected):

{
  "enabled": true,
  "available": true,
  "connected": true,
  "server": "ldaps://ldap.corp.example.com"
}

LDAP status response (not configured):

{
  "enabled": false,
  "available": false,
  "message": "LDAP not configured"
}

LDAP status response (ldap3 library not installed):

{
  "enabled": true,
  "available": false,
  "message": "LDAP configured but ldap3 library not installed"
}

LDAP status response (connection failed):

{
  "enabled": true,
  "available": true,
  "connected": false,
  "error": "Connection refused"
}

Startup and Shutdown

Startup Sequence

During application startup (lifespan()), authentication subsystems are initialized in order:

# 1. OAuth providers (6 providers)
auth_config = get_auth_config()
oauth_providers = setup_oauth_providers({
    "github": auth_config.oauth.github,
    "google": auth_config.oauth.google,
    "gitlab": auth_config.oauth.gitlab,
    "keycloak": auth_config.oauth.keycloak,
    "sourcecraft": auth_config.oauth.sourcecraft,
    "gitverse": auth_config.oauth.gitverse,
})
# Logs: "OAuth providers initialized: 2" or "No OAuth providers configured"

# 2. Yandex Cloud IAM (optional)
if settings.iam_enabled:
    setup_iam_validator(enabled=True, validation_url=settings.iam_validation_url)
# Logs: "Yandex Cloud IAM validator initialized" or "IAM auth disabled"

# 3. LDAP authenticator
ldap = setup_ldap_authenticator(auth_config.ldap)
# Logs: "LDAP authenticator initialized: ldaps://..." or "LDAP not configured"

Shutdown Cleanup

On shutdown, OAuth HTTP clients are closed gracefully:

await get_oauth_manager().close_all()

Graceful Degradation

All subsystems are optional. If initialization fails (missing library, bad config), the application logs a warning and continues:

WARNING  OAuth initialization skipped: <error>
WARNING  IAM initialization skipped: <error>
WARNING  LDAP initialization skipped: <error>

All other endpoints (local JWT login, API keys, query, scenarios) remain fully functional.


Troubleshooting

OAuth providers show enabled: false

Verify environment variables are set:

echo $OAUTH_GITHUB_CLIENT_ID
echo $OAUTH_GITHUB_CLIENT_SECRET

Both CLIENT_ID and CLIENT_SECRET must be non-empty for a provider to register.

OAuth callback returns invalid_state_parameter

The CSRF state token is stored in server memory. This error occurs if: - The state expired (server restarted between redirect and callback) - The state was already consumed (browser refresh) - Multi-instance deployment without shared state storage (use Redis in production)

LDAP returns 503 ldap_not_configured

The LDAP_SERVER environment variable is not set, or the ldap3 library is not installed:

pip install ldap3>=2.9.1

LDAP connection test fails

Check network connectivity and credentials:

# Test LDAP connectivity
ldapsearch -H ldaps://ldap.corp.example.com -x -D "cn=codegraph-svc,..." -w PASSWORD -b "dc=corp,dc=example,dc=com" "(sAMAccountName=testuser)"

LDAP role mapping always returns analyst

The LDAPAuthenticator.map_groups_to_role() method has a known attribute name mismatch: it accesses config.group_role_mapping instead of config.role_mapping. Until this is fixed in ldap_auth.py, all LDAP users receive the default analyst role regardless of role_mapping configuration.

No SIEM events appear

Verify SIEM dispatcher is enabled in config.yaml:

siem:
  enabled: true
  handlers:
    - type: syslog
      host: "siem.corp.example.com"
      port: 514

See SIEM Integration for full configuration.

Startup logs show initialization warnings

Check application logs for specific errors:

uvicorn src.api.main:app --log-level debug 2>&1 | grep -i "oauth\|ldap\|iam"