OAuth2/OIDC and LDAP/AD Integration¶
Configuration Guide for IT and Security Teams
Table of Contents¶
- Overview
- Key Capabilities
- Architecture
- Authentication Flow
- Provider Registration
- OAuth2/OIDC
- Supported Providers
- Configuration
- Authorization Flow
- User Provisioning
- LDAP/Active Directory
- Configuration
- Group-to-Role Mapping
- User Provisioning
- SIEM Integration
- Authentication Events
- Event Examples
- API Reference
- OAuth Endpoints
- LDAP Endpoints
- Startup and Shutdown
- Troubleshooting
- Related Documents
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 |
|---|---|
| 4 OAuth2 providers | GitHub, GitLab, Google, Keycloak (OIDC) |
| 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 both subsystems:
- OAuth —
setup_oauth_providers()readsOAUTH_*environment variables, registers enabled providers withOAuthManager - LDAP —
setup_ldap_authenticator()readsLDAP_*environment variables, creates the singletonLDAPAuthenticator
Both blocks are wrapped in try/except — the application starts normally even if no SSO is configured.
OAuth2/OIDC¶
Supported Providers¶
| Provider | Authorize URL | Token URL | Scopes |
|---|---|---|---|
| GitHub | github.com/login/oauth/authorize |
github.com/login/oauth/access_token |
read:user, user:email |
| GitLab | gitlab.com/oauth/authorize (or self-hosted) |
gitlab.com/oauth/token |
read_user, openid |
accounts.google.com/o/oauth2/v2/auth |
oauth2.googleapis.com/token |
openid, profile, email |
|
| Keycloak | {server}/realms/{realm}/protocol/openid-connect/auth |
.../token |
openid, profile, email |
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"
Providers without CLIENT_ID and CLIENT_SECRET are skipped automatically.
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 |
display_name |
Provider name field |
auth_provider |
oauth |
role |
Default: ANALYST |
external_id |
Provider’s unique user ID |
Subsequent logins match by (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 configuration 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. The authenticator 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 |
LDAP User Provisioning¶
On first LDAP login, a user is created:
| Field | Source |
|---|---|
username |
LDAP sAMAccountName (or configured attribute) |
email |
LDAP mail attribute |
display_name |
LDAP displayName attribute |
ldap_dn |
Full distinguished name |
auth_provider |
ldap |
role |
From group-to-role mapping |
Subsequent logins match by ldap_dn.
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 all 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:
[
{"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 (configured):
{
"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"
}
Startup and Shutdown¶
Startup Sequence¶
During application startup (lifespan()), OAuth and LDAP are initialized in order:
# 1. OAuth 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,
})
# Logs: "OAuth providers initialized: 2" or "No OAuth providers configured"
# 2. 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¶
Both subsystems are optional. If initialization fails (missing library, bad config), the application logs a warning and continues:
WARNING OAuth 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)"
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"
Related Documents¶
- RBAC and Authorization — Role hierarchy and permission catalog
- SIEM Integration — Security event formats and dispatcher configuration
- Security Brief — Enterprise security overview
- DLP Security — Data loss prevention
- Deployment Guide — High-availability deployment