Skip to main content

Single Sign-On (SSO)

FieldValue
Document IDASCEND-SEC-015
Version1.0.0
Last UpdatedDecember 19, 2025
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.

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: 1.0.0 | Last Updated: December 2025