Single Sign-On (SSO)
| Field | Value |
|---|---|
| Document ID | ASCEND-SEC-015 |
| Version | 2026.04 |
| Last Updated | April 2026 |
| Author | Ascend Engineering Team |
| Publisher | OW-KAI Technologies Inc. |
| Classification | Enterprise Client Documentation |
| Compliance | SOC 2 CC6.1/CC6.2, PCI-DSS 7.1/8.3, HIPAA 164.312, NIST 800-53 AC-2/SI-4 |
Reading Time: 10 minutes | Skill Level: Advanced
Overview
ASCEND supports enterprise SSO integration with Okta, Azure Active Directory, and Google Workspace using OIDC. SSO provides centralized authentication, automatic group-to-role mapping, and seamless user provisioning.
Fail-Secure Behavior
If the SSO provider is unreachable during authentication, the login attempt fails. ASCEND does not fall back to password authentication when SSO is configured as the primary auth method.
Supported Providers
| Provider | Protocol | Group Sync | MFA |
|---|---|---|---|
| Okta | OIDC | Yes | Delegated |
| Azure AD | OIDC | Yes | Delegated |
| Google Workspace | OIDC | Limited | Delegated |
SSO Flow
+---------------------------------------------------------------------------------+
| SSO AUTHENTICATION FLOW |
+---------------------------------------------------------------------------------+
| |
| USER ASCEND IDENTITY PROVIDER |
| | | | |
| | 1. Click SSO Login | | |
| |----------------------->| | |
| | | | |
| | 2. Redirect to IdP | 3. Authorization URL | |
| |<-----------------------|----------------------------->| |
| | | | |
| | 4. Authenticate with IdP (MFA) | |
| |------------------------------------------------------>| |
| | | | |
| | 5. Redirect with code | | |
| |<------------------------------------------------------| |
| | | | |
| | 6. Code to ASCEND | | |
| |----------------------->| | |
| | | 7. Exchange code for token | |
| | |----------------------------->| |
| | | | |
| | | 8. Access token + ID token | |
| | |<-----------------------------| |
| | | | |
| | | 9. Validate token (JWKS) | |
| | |----------------------------->| |
| | | | |
| | | 10. Get user info + groups | |
| | |----------------------------->| |
| | | | |
| | 11. Create session | | |
| |<-----------------------| | |
| | | | |
+---------------------------------------------------------------------------------+
Configuration
Okta Setup
# Source: sso_manager.py:28
okta_config = {
"name": "Okta",
"client_id": "<okta-client-id>",
"client_secret": "<okta-client-secret>",
"domain": "your-org.okta.com",
"authorization_url": "https://your-org.okta.com/oauth2/v1/authorize",
"token_url": "https://your-org.okta.com/oauth2/v1/token",
"userinfo_url": "https://your-org.okta.com/oauth2/v1/userinfo",
"jwks_url": "https://your-org.okta.com/oauth2/v1/keys",
"scopes": ["openid", "profile", "email", "groups"]
}
Azure AD Setup
# Source: sso_manager.py:40
azure_config = {
"name": "Azure Active Directory",
"client_id": "<azure-client-id>",
"client_secret": "<azure-client-secret>",
"tenant_id": "<your-tenant-id>",
"authorization_url": "https://login.microsoftonline.com/<tenant>/oauth2/v2.0/authorize",
"token_url": "https://login.microsoftonline.com/<tenant>/oauth2/v2.0/token",
"userinfo_url": "https://graph.microsoft.com/v1.0/me",
"jwks_url": "https://login.microsoftonline.com/common/discovery/v2.0/keys",
"scopes": ["openid", "profile", "email", "User.Read", "Directory.Read.All"]
}
Google Workspace Setup
# Source: sso_manager.py:52
google_config = {
"name": "Google Workspace",
"client_id": "<google-client-id>",
"client_secret": "<google-client-secret>",
"authorization_url": "https://accounts.google.com/o/oauth2/auth",
"token_url": "https://oauth2.googleapis.com/token",
"userinfo_url": "https://www.googleapis.com/oauth2/v2/userinfo",
"jwks_url": "https://www.googleapis.com/oauth2/v3/certs",
"scopes": ["openid", "email", "profile"]
}
SSO API Endpoints
Get Authorization URL
curl "https://pilot.owkai.app/api/sso/authorize?provider=okta&redirect_uri=https://app.company.com/callback" \
-H "Authorization: Bearer owkai_..."
Response:
{
"authorization_url": "https://your-org.okta.com/oauth2/v1/authorize?client_id=...&redirect_uri=...&scope=openid+profile+email+groups&response_type=code&state=...",
"state": "abc123xyz"
}
Exchange Code for Token
curl -X POST "https://pilot.owkai.app/api/sso/callback" \
-H "Content-Type: application/json" \
-d '{
"provider": "okta",
"code": "<authorization_code>",
"redirect_uri": "https://app.company.com/callback",
"state": "abc123xyz"
}'
Response:
{
"success": true,
"user": {
"email": "user@company.com",
"first_name": "John",
"last_name": "Doe",
"access_level": 4,
"role": "admin",
"sso_provider": "okta",
"groups": ["OW-AI-Administrators", "IT-Security"]
},
"session_token": "<jwt_token>",
"expires_at": "2025-12-15T18:30:00Z"
}
Group-to-Role Mapping
Default Mappings
# Source: sso_manager.py:64
GROUP_TO_ROLE_MAPPING = {
# Okta Groups
"OW-AI-Executives": 5, # Executive level
"OW-AI-Administrators": 4, # Admin level
"OW-AI-Managers": 3, # Manager level
"OW-AI-PowerUsers": 2, # Power user level
"OW-AI-BasicUsers": 1, # Basic level
"OW-AI-Restricted": 0, # Restricted level
# Azure AD Groups
"OW-AI Executive Team": 5,
"OW-AI System Administrators": 4,
"OW-AI Security Managers": 3,
"OW-AI Power Users": 2,
"OW-AI Standard Users": 1,
# Google Workspace Groups
"ow-ai-executives@company.com": 5,
"ow-ai-admins@company.com": 4,
"ow-ai-managers@company.com": 3,
"ow-ai-users@company.com": 1
}
Access Levels
| Level | Role | Permissions |
|---|---|---|
| 5 | Executive | Full access + executive dashboard |
| 4 | Admin | Full administrative access |
| 3 | Manager | Team management + approvals |
| 2 | Power User | Advanced features |
| 1 | Basic User | Standard access |
| 0 | Restricted | Read-only access |
Role Assignment Logic
# Source: sso_manager.py:240
def map_groups_to_access_level(self, groups: List[str]) -> int:
"""Map IdP groups to OW-AI access level."""
# Find highest access level from user's groups
max_level = 0
for group in groups:
if group in self.group_to_role_mapping:
level = self.group_to_role_mapping[group]
if level > max_level:
max_level = level
return max_level
Token Validation
JWKS Verification (SEC-079)
# Source: sso_manager.py:322
def validate_sso_token(self, provider: str, id_token: str) -> Dict:
"""
Validate SSO ID token with full cryptographic signature verification.
Security (SEC-079):
- Fetches JWKS from provider to get signing keys
- Validates RS256 signature cryptographically
- Validates audience, issuer, and expiration claims
"""
# Get signing key from JWKS
jwks_client = PyJWKClient(provider_config["jwks_url"])
signing_key = jwks_client.get_signing_key_from_jwt(id_token)
# Full cryptographic verification
decoded_token = jwt.decode(
id_token,
signing_key.key,
algorithms=["RS256"],
options={
"verify_signature": True,
"verify_exp": True,
"verify_iat": True,
"require": ["exp", "iat", "sub", "email"]
}
)
return decoded_token
User Provisioning
Automatic User Creation
# Source: sso_manager.py:257
def create_enterprise_user_profile(self, provider, user_info, groups):
"""Create enterprise user profile from SSO data."""
access_level = self.map_groups_to_access_level(groups)
return {
"email": user_info.get("email"),
"first_name": user_info.get("given_name"),
"last_name": user_info.get("family_name"),
"access_level": access_level,
"sso_provider": provider,
"sso_groups": groups,
"role": "admin" if access_level >= 4 else "user",
"department": self._extract_department_from_groups(groups),
"mfa_enabled": True, # SSO providers enforce MFA
"status": "Active",
"login_method": "SSO"
}
Department Extraction
# Source: sso_manager.py:300
DEPARTMENT_MAPPINGS = {
"finance": "Finance",
"hr": "Human Resources",
"it": "Information Technology",
"security": "Security",
"operations": "Operations",
"executive": "Executive",
"legal": "Legal",
"marketing": "Marketing"
}
Security Considerations
1. Token Security
- ID tokens validated with JWKS public keys
- RS256 signature verification
- Audience validation
- Expiration checking
2. State Parameter
# CSRF protection via state parameter
state = secrets.token_urlsafe(32)
# Store state and verify on callback
3. MFA Delegation
# MFA enforced by identity provider
{
"mfa_enabled": True,
"mfa_provider": "okta"
}
4. Group Sync
# Groups refreshed on each login
groups = sso.get_user_groups(provider, access_token)
access_level = sso.map_groups_to_access_level(groups)
Environment Variables
| Variable | Description | Example |
|---|---|---|
OKTA_DOMAIN | Okta organization domain | your-org.okta.com |
OKTA_CLIENT_ID | Okta application client ID | 0oa... |
OKTA_CLIENT_SECRET | Okta application secret | (secret) |
AZURE_TENANT_ID | Azure AD tenant ID | xxxxxxxx-xxxx-... |
AZURE_CLIENT_ID | Azure application ID | xxxxxxxx-xxxx-... |
AZURE_CLIENT_SECRET | Azure application secret | (secret) |
GOOGLE_CLIENT_ID | Google OAuth client ID | xxxx.apps.google... |
GOOGLE_CLIENT_SECRET | Google OAuth secret | (secret) |
Best Practices
1. Configure Group Mappings
# Map all relevant IdP groups
GROUP_TO_ROLE_MAPPING = {
"Security-Team": 4,
"Compliance-Officers": 3,
"All-Employees": 1
}
2. Enable Group Sync
# Ensure groups scope is requested
scopes = ["openid", "profile", "email", "groups"]
3. Regular Access Reviews
# Review access levels periodically
for user in users:
current_groups = sso.get_user_groups(provider, token)
expected_level = sso.map_groups_to_access_level(current_groups)
if user.access_level != expected_level:
update_user_access(user, expected_level)
4. Monitor SSO Events
# Log all SSO authentication events
{
"event_type": "SSO_LOGIN",
"provider": "okta",
"user_email": "user@company.com",
"groups": ["OW-AI-Administrators"],
"access_level": 4
}
Next Steps
- Authentication — JWT session management
- RBAC — Role-based access control
- Multi-Tenancy — Organization isolation
Document Version: 2026.04 | Last Updated: April 2026