Skip to main content

Single Sign-On (SSO)

FieldValue
Document IDASCEND-SEC-015
Version2026.04
Last UpdatedApril 2026
AuthorAscend Engineering Team
PublisherOW-KAI Technologies Inc.
ClassificationEnterprise Client Documentation
ComplianceSOC 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

ProviderProtocolGroup SyncMFA
OktaOIDCYesDelegated
Azure ADOIDCYesDelegated
Google WorkspaceOIDCLimitedDelegated

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

LevelRolePermissions
5ExecutiveFull access + executive dashboard
4AdminFull administrative access
3ManagerTeam management + approvals
2Power UserAdvanced features
1Basic UserStandard access
0RestrictedRead-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

VariableDescriptionExample
OKTA_DOMAINOkta organization domainyour-org.okta.com
OKTA_CLIENT_IDOkta application client ID0oa...
OKTA_CLIENT_SECRETOkta application secret(secret)
AZURE_TENANT_IDAzure AD tenant IDxxxxxxxx-xxxx-...
AZURE_CLIENT_IDAzure application IDxxxxxxxx-xxxx-...
AZURE_CLIENT_SECRETAzure application secret(secret)
GOOGLE_CLIENT_IDGoogle OAuth client IDxxxx.apps.google...
GOOGLE_CLIENT_SECRETGoogle 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


Document Version: 2026.04 | Last Updated: April 2026