feat: add generic OIDC provider for self-hosted identity providers

Adds support for any OpenID Connect compliant identity provider:
- Authentik
- Authelia
- Keycloak
- Okta
- Any other OIDC provider

Configuration via environment variables:
- SOCIAL_AUTH_OIDC_OIDC_ENDPOINT: Discovery endpoint
- SOCIAL_AUTH_OIDC_KEY: Client ID
- SOCIAL_AUTH_OIDC_SECRET: Client secret
- SOCIAL_AUTH_OIDC_NAME: Optional custom button text

Closes #140

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Brian Olson
2025-12-31 16:27:08 -05:00
parent 3aa321aff8
commit 33628af31f
4 changed files with 46 additions and 2 deletions

View File

@@ -52,9 +52,29 @@ Only providers with credentials configured will appear as login options. Set the
| Google | `SOCIAL_AUTH_GOOGLE_OAUTH2_KEY`, `SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET` | | Google | `SOCIAL_AUTH_GOOGLE_OAUTH2_KEY`, `SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET` |
| Microsoft | `SOCIAL_AUTH_MICROSOFT_GRAPH_KEY`, `SOCIAL_AUTH_MICROSOFT_GRAPH_SECRET` | | Microsoft | `SOCIAL_AUTH_MICROSOFT_GRAPH_KEY`, `SOCIAL_AUTH_MICROSOFT_GRAPH_SECRET` |
| Auth0 | `SOCIAL_AUTH_AUTH0_DOMAIN`, `SOCIAL_AUTH_AUTH0_KEY`, `SOCIAL_AUTH_AUTH0_SECRET` | | Auth0 | `SOCIAL_AUTH_AUTH0_DOMAIN`, `SOCIAL_AUTH_AUTH0_KEY`, `SOCIAL_AUTH_AUTH0_SECRET` |
| Generic OIDC | `SOCIAL_AUTH_OIDC_OIDC_ENDPOINT`, `SOCIAL_AUTH_OIDC_KEY`, `SOCIAL_AUTH_OIDC_SECRET` |
For Auth0, set the callback URL to: `https://your-domain/api/1/auth/social/complete/auth0/` For Auth0, set the callback URL to: `https://your-domain/api/1/auth/social/complete/auth0/`
### Generic OIDC Provider
The generic OIDC provider works with any OpenID Connect compliant identity provider, including:
- **Authentik** - Self-hosted identity provider
- **Authelia** - Self-hosted authentication server
- **Keycloak** - Open source identity management
- **Okta** - Enterprise identity platform
- And any other OIDC-compliant provider
Configuration:
- `SOCIAL_AUTH_OIDC_OIDC_ENDPOINT`: The OIDC discovery endpoint (e.g., `https://authentik.example.com/application/o/<app-slug>/`)
- `SOCIAL_AUTH_OIDC_KEY`: Client ID from your identity provider
- `SOCIAL_AUTH_OIDC_SECRET`: Client secret from your identity provider
- `SOCIAL_AUTH_OIDC_NAME` (optional): Custom button text (default: "SSO")
Set the callback URL to: `https://your-domain/api/1/auth/social/complete/oidc/`
**Note on MFA:** Multi-factor authentication is handled by your identity provider. Enable MFA in Authentik, Authelia, or your chosen provider to require 2FA for Tabby Web logins.
## Adding Tabby app versions ## Adding Tabby app versions
* `docker-compose run tabby /manage.sh add_version 1.0.163` * `docker-compose run tabby /manage.sh add_version 1.0.163`

View File

@@ -37,6 +37,12 @@ PROVIDER_CONFIG = {
'cls': 'btn-dark', 'cls': 'btn-dark',
'env_prefix': 'SOCIAL_AUTH_AUTH0', 'env_prefix': 'SOCIAL_AUTH_AUTH0',
}, },
'oidc': {
'name': 'SSO', # Generic name, can be overridden via SOCIAL_AUTH_OIDC_NAME
'icon': 'openid', # OpenID icon
'cls': 'btn-info',
'env_prefix': 'SOCIAL_AUTH_OIDC',
},
} }
@@ -48,9 +54,19 @@ def is_provider_configured(env_prefix: str) -> bool:
if env_prefix == 'SOCIAL_AUTH_AUTH0': if env_prefix == 'SOCIAL_AUTH_AUTH0':
domain = getattr(settings, f'{env_prefix}_DOMAIN', None) domain = getattr(settings, f'{env_prefix}_DOMAIN', None)
return bool(key and secret and domain) return bool(key and secret and domain)
# For generic OIDC, also need OIDC_ENDPOINT
if env_prefix == 'SOCIAL_AUTH_OIDC':
endpoint = getattr(settings, f'{env_prefix}_OIDC_ENDPOINT', None)
return bool(key and secret and endpoint)
return bool(key and secret) return bool(key and secret)
def get_provider_display_name(env_prefix: str, default_name: str) -> str:
"""Get custom display name for a provider, if configured."""
custom_name = getattr(settings, f'{env_prefix}_NAME', None)
return custom_name if custom_name else default_name
class LogoutView(APIView): class LogoutView(APIView):
def post(self, request, format=None): def post(self, request, format=None):
logout(request) logout(request)
@@ -66,7 +82,9 @@ class ProvidersView(APIView):
if is_provider_configured(config['env_prefix']): if is_provider_configured(config['env_prefix']):
providers.append({ providers.append({
'id': provider_id, 'id': provider_id,
'name': config['name'], 'name': get_provider_display_name(
config['env_prefix'], config['name']
),
'icon': config['icon'], 'icon': config['icon'],
'cls': config['cls'], 'cls': config['cls'],
}) })

View File

@@ -141,6 +141,7 @@ AUTHENTICATION_BACKENDS = (
"social_core.backends.microsoft.MicrosoftOAuth2", "social_core.backends.microsoft.MicrosoftOAuth2",
"social_core.backends.google.GoogleOAuth2", "social_core.backends.google.GoogleOAuth2",
"social_core.backends.auth0.Auth0OAuth2", "social_core.backends.auth0.Auth0OAuth2",
"social_core.backends.open_id_connect.OpenIdConnectAuth",
"django.contrib.auth.backends.ModelBackend", "django.contrib.auth.backends.ModelBackend",
) )
@@ -188,6 +189,10 @@ for key in [
"SOCIAL_AUTH_AUTH0_DOMAIN", "SOCIAL_AUTH_AUTH0_DOMAIN",
"SOCIAL_AUTH_AUTH0_KEY", "SOCIAL_AUTH_AUTH0_KEY",
"SOCIAL_AUTH_AUTH0_SECRET", "SOCIAL_AUTH_AUTH0_SECRET",
"SOCIAL_AUTH_OIDC_OIDC_ENDPOINT",
"SOCIAL_AUTH_OIDC_KEY",
"SOCIAL_AUTH_OIDC_SECRET",
"SOCIAL_AUTH_OIDC_NAME",
"CONNECTION_GATEWAY_AUTH_CA", "CONNECTION_GATEWAY_AUTH_CA",
"CONNECTION_GATEWAY_AUTH_CERTIFICATE", "CONNECTION_GATEWAY_AUTH_CERTIFICATE",
"CONNECTION_GATEWAY_AUTH_KEY", "CONNECTION_GATEWAY_AUTH_KEY",

View File

@@ -2,7 +2,7 @@ import { Component } from '@angular/core'
import { HttpClient } from '@angular/common/http' import { HttpClient } from '@angular/common/http'
import { LoginService, CommonService } from 'src/common' import { LoginService, CommonService } from 'src/common'
import { faGithub, faGitlab, faGoogle, faMicrosoft, IconDefinition } from '@fortawesome/free-brands-svg-icons' import { faGithub, faGitlab, faGoogle, faMicrosoft, faOpenid, IconDefinition } from '@fortawesome/free-brands-svg-icons'
import { faKey } from '@fortawesome/free-solid-svg-icons' import { faKey } from '@fortawesome/free-solid-svg-icons'
interface Provider { interface Provider {
@@ -18,6 +18,7 @@ const iconMap: Record<string, IconDefinition> = {
gitlab: faGitlab, gitlab: faGitlab,
google: faGoogle, google: faGoogle,
microsoft: faMicrosoft, microsoft: faMicrosoft,
openid: faOpenid, // Used for generic OIDC providers (Authentik, Authelia, Keycloak, etc.)
key: faKey, // Used for Auth0 and other providers without brand icons key: faKey, // Used for Auth0 and other providers without brand icons
} }