zero trust architecture

Implementing Zero Trust for SaaS Applications

Implementing zero trust access controls for SaaS applications using CASB, SSPM, conditional access policies, OAuth app governance, and session controls to enforce identity verification, device compliance, and data protection for cloud-hosted services.

casbconditional-accessoauth-governancesaas-securitysession-controlssspmzero-trust
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

When to Use

  • When securing access to SaaS applications (Microsoft 365, Google Workspace, Salesforce, Slack)
  • When implementing conditional access policies requiring MFA and device compliance for SaaS
  • When deploying CASB for shadow IT discovery and unsanctioned app blocking
  • When enforcing session-level controls (DLP, download restrictions) for sensitive SaaS data
  • When governing OAuth application permissions and detecting excessive consent grants

Do not use as a replacement for SaaS-native security controls (configure those first), for applications with no SAML/OIDC support, or when SaaS vendor does not support API integration for CASB/SSPM.

Prerequisites

  • Identity provider with conditional access: Microsoft Entra ID P1/P2, Okta
  • CASB solution: Microsoft Defender for Cloud Apps, Netskope, or Zscaler CASB
  • SaaS applications configured with SSO via SAML 2.0 or OIDC
  • MDM enrollment for device compliance signals (Intune, Jamf)
  • DLP policies defined for sensitive data categories

Workflow

Step 1: Federate SaaS Authentication Through Identity Provider

Centralize authentication for all SaaS applications through a single IdP.

# Configure SAML SSO for Salesforce via Entra ID
Connect-MgGraph -Scopes "Application.ReadWrite.All"
 
# Create enterprise application for Salesforce
$app = New-MgServicePrincipal -AppId "SALESFORCE_APP_ID" -DisplayName "Salesforce"
 
# Configure SAML SSO settings
$samlSettings = @{
    preferredSingleSignOnMode = "saml"
    samlSingleSignOnSettings = @{
        relayState = ""
    }
}
Update-MgServicePrincipal -ServicePrincipalId $app.Id -BodyParameter $samlSettings
 
# Assign user groups to the application
New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $app.Id -BodyParameter @{
    principalId = "SALES_GROUP_ID"
    resourceId = $app.Id
    appRoleId = "DEFAULT_ROLE_ID"
}

Step 2: Create Conditional Access Policies for SaaS Applications

Enforce identity and device requirements before granting SaaS access.

# Block access from non-compliant devices to sensitive SaaS apps
$policy = @{
    displayName = "ZT - Require Compliant Device for SaaS"
    state = "enabled"
    conditions = @{
        applications = @{
            includeApplications = @("SALESFORCE_APP_ID", "M365_APP_ID", "SLACK_APP_ID")
        }
        users = @{
            includeUsers = @("All")
            excludeGroups = @("BREAK_GLASS_GROUP")
        }
        clientAppTypes = @("browser", "mobileAppsAndDesktopClients")
    }
    grantControls = @{
        operator = "AND"
        builtInControls = @("mfa", "compliantDevice")
    }
    sessionControls = @{
        cloudAppSecurity = @{
            isEnabled = $true
            cloudAppSecurityType = "mcasConfigured"
        }
        signInFrequency = @{
            value = 8
            type = "hours"
            isEnabled = $true
        }
    }
}
New-MgIdentityConditionalAccessPolicy -BodyParameter $policy
 
# Block downloads on unmanaged devices
$downloadPolicy = @{
    displayName = "ZT - Block Downloads on Unmanaged Devices"
    state = "enabled"
    conditions = @{
        applications = @{ includeApplications = @("SHAREPOINT_APP_ID") }
        users = @{ includeUsers = @("All") }
        devices = @{
            deviceFilter = @{
                mode = "include"
                rule = "device.isCompliant -ne True -or device.trustType -ne 'ServerAD'"
            }
        }
    }
    sessionControls = @{
        cloudAppSecurity = @{
            isEnabled = $true
            cloudAppSecurityType = "mcasConfigured"
        }
    }
}
New-MgIdentityConditionalAccessPolicy -BodyParameter $downloadPolicy

Step 3: Deploy CASB for Shadow IT Discovery and App Governance

Configure Microsoft Defender for Cloud Apps to discover and control SaaS usage.

# Query discovered cloud apps via Defender for Cloud Apps API
curl -X GET "https://api.cloudappsecurity.com/api/v1/discovery/" \
  -H "Authorization: Token ${MDCA_API_TOKEN}" \
  -H "Content-Type: application/json"
 
# Get list of unsanctioned apps
curl -X GET "https://api.cloudappsecurity.com/api/v1/discovery/discovered_apps/" \
  -H "Authorization: Token ${MDCA_API_TOKEN}" \
  -d '{
    "filters": {
      "appTag": {"eq": "unsanctioned"},
      "traffic": {"gte": 1000}
    },
    "sortField": "traffic",
    "sortDirection": "desc"
  }'
 
# Create session policy for DLP enforcement
curl -X POST "https://api.cloudappsecurity.com/api/v1/policies/" \
  -H "Authorization: Token ${MDCA_API_TOKEN}" \
  -d '{
    "name": "Block PII Upload to SaaS",
    "policyType": "SESSION",
    "severity": "HIGH",
    "enabled": true,
    "sessionPolicyType": "CONTROL_UPLOAD",
    "filters": {
      "fileType": {"eq": ["DOCUMENT", "SPREADSHEET"]},
      "contentInspection": {
        "dataType": ["CREDIT_CARD", "SSN", "PASSPORT"]
      }
    },
    "actions": {
      "block": true,
      "notify": {
        "emailRecipients": ["security-team@company.com"]
      }
    }
  }'

Step 4: Configure OAuth App Governance

Review and restrict OAuth application permissions to prevent excessive consent.

# Query OAuth apps with high-privilege permissions
$oauthApps = Invoke-MgGraphRequest -Method GET `
  "https://graph.microsoft.com/v1.0/servicePrincipals?\$filter=tags/any(t:t eq 'WindowsAzureActiveDirectoryIntegratedApp')&\$select=displayName,appId,oauth2PermissionScopes"
 
# Review consent grants
$grants = Get-MgOauth2PermissionGrant -All
$highRisk = $grants | Where-Object {
    $_.Scope -match "Mail.ReadWrite|Files.ReadWrite.All|Directory.ReadWrite.All"
}
 
Write-Host "High-risk OAuth grants: $($highRisk.Count)"
$highRisk | ForEach-Object {
    $sp = Get-MgServicePrincipal -ServicePrincipalId $_.ClientId
    Write-Host "  App: $($sp.DisplayName) | Scope: $($_.Scope) | Type: $($_.ConsentType)"
}
 
# Configure app consent policy to require admin approval
$consentPolicy = @{
    displayName = "Require Admin Approval for High-Risk Permissions"
    conditions = @{
        clientApplications = @{ includeAllClientApplications = $true }
        permissions = @{
            permissionClassification = "high"
            permissions = @(
                @{ permissionValue = "Mail.ReadWrite"; permissionType = "delegated" }
                @{ permissionValue = "Files.ReadWrite.All"; permissionType = "delegated" }
            )
        }
    }
}

Step 5: Implement SaaS Security Posture Management (SSPM)

Audit and remediate SaaS security configuration drift.

# Query SaaS security posture via CASB API
curl -X GET "https://api.cloudappsecurity.com/api/v1/security_config/" \
  -H "Authorization: Token ${MDCA_API_TOKEN}" \
  -d '{"app": "Microsoft 365"}'
 
# Common SSPM checks:
# - MFA enforcement for all admin accounts
# - External sharing restrictions in SharePoint/OneDrive
# - Email forwarding rules to external domains blocked
# - Idle session timeout configured (< 8 hours)
# - Legacy authentication protocols disabled
# - Admin consent workflow enabled
# - Conditional access policies active
# - Audit logging enabled for all services

Key Concepts

Term Definition
CASB Cloud Access Security Broker - intermediary enforcing security policies between users and SaaS applications
SSPM SaaS Security Posture Management - continuous monitoring of SaaS application security configurations
OAuth Governance Review and control of third-party application permissions granted through OAuth consent flows
Session Controls Real-time access restrictions (block downloads, DLP inspection, watermarking) applied during active SaaS sessions
Shadow IT Unauthorized SaaS applications used by employees without IT approval or security review
Conditional Access Policy engine evaluating identity, device, location, and risk signals before granting SaaS access

Tools & Systems

  • Microsoft Defender for Cloud Apps: CASB providing shadow IT discovery, session controls, DLP, and SSPM
  • Microsoft Entra ID Conditional Access: Policy engine for identity-based access control to SaaS applications
  • Netskope CASB: Cloud-native CASB with inline and API-based SaaS security controls
  • Okta Identity Governance: OAuth app governance and access certification for SaaS applications
  • SSPM Tools: AppOmni, Adaptive Shield, Valence Security for SaaS configuration monitoring

Common Scenarios

Scenario: Securing Microsoft 365 and Salesforce for 1,000-User Organization

Context: A professional services firm with 1,000 users uses Microsoft 365, Salesforce, Slack, and 20+ other SaaS apps. Several data breaches in the industry drive a zero trust initiative for all SaaS access.

Approach:

  1. Federate all SaaS authentication through Entra ID with SAML SSO
  2. Create conditional access policies requiring MFA + compliant device for all SaaS apps
  3. Deploy Defender for Cloud Apps for shadow IT discovery (identify 150+ unauthorized apps)
  4. Mark unauthorized apps as unsanctioned and block via SWG/proxy
  5. Configure session controls: block downloads on unmanaged devices, DLP for file uploads
  6. Review OAuth app permissions: revoke 45 high-risk consent grants, enable admin approval workflow
  7. Enable SSPM monitoring for Microsoft 365 and Salesforce configurations
  8. Set up weekly automated posture reports for security leadership

Pitfalls: Conditional access policies need break-glass exclusions. Some legacy SaaS apps may not support modern authentication. Session controls require proxy-based CASB which can impact performance. OAuth app revocation may break integrations; coordinate with app owners first.

Output Format

Zero Trust SaaS Security Report
==================================================
Organization: ProServices Corp
Report Date: 2026-02-23
 
SAAS INVENTORY:
  Sanctioned Apps: 25
  Unsanctioned (blocked): 127
  Shadow IT Users: 342 (discovered in last 30 days)
 
CONDITIONAL ACCESS:
  Policies active: 8
  Sign-ins evaluated: 456,789
  Blocked by policy: 2,345 (0.5%)
  MFA enforced: 100% of sign-ins
 
DEVICE COMPLIANCE:
  Compliant device required: All 25 sanctioned apps
  Sign-ins from compliant: 448,123 (98.1%)
  Sign-ins blocked (non-compliant): 8,666
 
CASB / DLP:
  DLP violations detected: 89
  Files blocked from upload: 34
  Downloads blocked (unmanaged): 1,234
 
OAUTH GOVERNANCE:
  Total OAuth apps: 312
  High-risk permissions: 12 (reviewed)
  Revoked consents: 45
  Pending admin approval: 8
 
SSPM FINDINGS:
  Critical misconfigurations: 3
  High: 7
  Medium: 15
  Remediated this month: 18
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 1

api-reference.md1.7 KB

API Reference: Zero Trust for SaaS Applications

Microsoft Graph API v1.0

Authentication

POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token
Body: grant_type=client_credentials&client_id=X&client_secret=Y&scope=https://graph.microsoft.com/.default

Conditional Access

Method Endpoint Description
GET /identity/conditionalAccess/policies List CA policies
GET /identity/conditionalAccess/policies/{id} Get policy details

Enterprise Applications

Method Endpoint Description
GET /servicePrincipals List service principals
GET /oauth2PermissionGrants List OAuth consent grants
GET /appRoleAssignments List app role assignments

Identity Protection

Method Endpoint Description
GET /identityProtection/riskyUsers List at-risk users
GET /identityProtection/riskDetections Risk detection events

CA Policy Grant Controls

Control Description
mfa Require multi-factor authentication
compliantDevice Require Intune-compliant device
domainJoinedDevice Require hybrid Azure AD join
passwordChange Force password change

Risky OAuth Scopes

Scope Risk
Mail.ReadWrite Full mailbox access
Files.ReadWrite.All All OneDrive/SharePoint files
Directory.ReadWrite.All Full directory modification

References

Scripts 1

agent.py5.8 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Agent for auditing zero trust controls on SaaS applications via Microsoft Graph API."""

import requests
import json
import argparse
from datetime import datetime, timezone

GRAPH_API = "https://graph.microsoft.com/v1.0"


def get_token(tenant_id, client_id, client_secret):
    """Acquire OAuth2 token for Microsoft Graph API."""
    url = f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token"
    data = {"grant_type": "client_credentials", "client_id": client_id,
            "client_secret": client_secret, "scope": "https://graph.microsoft.com/.default"}
    resp = requests.post(url, data=data, timeout=30)
    resp.raise_for_status()
    token = resp.json()["access_token"]
    print("[*] Authenticated to Microsoft Graph API")
    return {"Authorization": f"Bearer {token}"}


def list_conditional_access_policies(headers):
    """List Entra ID conditional access policies for SaaS apps."""
    url = f"{GRAPH_API}/identity/conditionalAccess/policies"
    resp = requests.get(url, headers=headers, timeout=30)
    resp.raise_for_status()
    policies = resp.json().get("value", [])
    findings = []
    print(f"\n[*] Conditional Access Policies: {len(policies)}")
    for p in policies:
        state = p.get("state", "disabled")
        conditions = p.get("conditions", {})
        grant_controls = p.get("grantControls", {})
        mfa_required = "mfa" in str(grant_controls.get("builtInControls", [])).lower()
        print(f"  [{'+' if state == 'enabled' else '-'}] {p['displayName']} "
              f"(state={state}, MFA={'Yes' if mfa_required else 'No'})")
        if state == "enabled" and not mfa_required:
            findings.append({"policy": p["displayName"], "issue": "No MFA required",
                             "severity": "HIGH"})
    return policies, findings


def list_enterprise_apps(headers):
    """List enterprise applications (service principals) for shadow IT discovery."""
    url = f"{GRAPH_API}/servicePrincipals"
    params = {"$top": 100, "$select": "displayName,appId,accountEnabled,signInAudience"}
    resp = requests.get(url, headers=headers, params=params, timeout=30)
    resp.raise_for_status()
    apps = resp.json().get("value", [])
    print(f"\n[*] Enterprise Applications: {len(apps)}")
    third_party = [a for a in apps if a.get("signInAudience") != "AzureADMyOrg"]
    print(f"  Third-party apps: {len(third_party)}")
    for a in third_party[:10]:
        print(f"  {a['displayName']} (enabled={a.get('accountEnabled', '?')})")
    return apps


def check_oauth_app_consents(headers):
    """Audit OAuth2 permission grants for overprivileged consents."""
    url = f"{GRAPH_API}/oauth2PermissionGrants"
    resp = requests.get(url, headers=headers, timeout=30)
    resp.raise_for_status()
    grants = resp.json().get("value", [])
    findings = []
    for g in grants:
        scope = g.get("scope", "")
        if any(perm in scope for perm in ["Mail.ReadWrite", "Files.ReadWrite.All",
                                           "Directory.ReadWrite.All", "User.ReadWrite.All"]):
            findings.append({
                "clientId": g.get("clientId"), "scope": scope,
                "consentType": g.get("consentType"), "severity": "HIGH",
            })
    print(f"\n[*] OAuth grants: {len(grants)} total, {len(findings)} overprivileged")
    for f in findings[:5]:
        print(f"  [!] {f['clientId']}: {f['scope'][:80]}")
    return findings


def check_sign_in_risk(headers, days=7):
    """Check risky sign-ins to SaaS applications."""
    url = f"{GRAPH_API}/identityProtection/riskyUsers"
    params = {"$filter": "riskState eq 'atRisk'", "$top": 50}
    resp = requests.get(url, headers=headers, params=params, timeout=30)
    if resp.status_code == 200:
        users = resp.json().get("value", [])
        print(f"\n[*] Risky users: {len(users)}")
        for u in users[:10]:
            print(f"  [!] {u.get('userDisplayName', 'N/A')} - risk: {u.get('riskLevel')}")
        return users
    return []


def generate_report(policies, ca_findings, oauth_findings, risky_users, output_path):
    """Generate SaaS zero trust audit report."""
    report = {
        "audit_date": datetime.now(timezone.utc).isoformat(),
        "summary": {
            "conditional_access_policies": len(policies),
            "ca_findings": len(ca_findings),
            "overprivileged_oauth": len(oauth_findings),
            "risky_users": len(risky_users),
        },
        "ca_findings": ca_findings,
        "oauth_findings": oauth_findings[:20],
        "risky_users": risky_users[:20],
    }
    with open(output_path, "w") as f:
        json.dump(report, f, indent=2, default=str)
    print(f"\n[*] Report saved to {output_path}")


def main():
    parser = argparse.ArgumentParser(description="SaaS Zero Trust Audit Agent")
    parser.add_argument("action", choices=["ca-policies", "apps", "oauth", "risk", "full-audit"])
    parser.add_argument("--tenant-id", required=True)
    parser.add_argument("--client-id", required=True)
    parser.add_argument("--client-secret", required=True)
    parser.add_argument("-o", "--output", default="saas_zt_audit.json")
    args = parser.parse_args()

    headers = get_token(args.tenant_id, args.client_id, args.client_secret)
    if args.action == "ca-policies":
        list_conditional_access_policies(headers)
    elif args.action == "apps":
        list_enterprise_apps(headers)
    elif args.action == "oauth":
        check_oauth_app_consents(headers)
    elif args.action == "risk":
        check_sign_in_risk(headers)
    elif args.action == "full-audit":
        policies, ca_f = list_conditional_access_policies(headers)
        list_enterprise_apps(headers)
        oauth_f = check_oauth_app_consents(headers)
        risky = check_sign_in_risk(headers)
        generate_report(policies, ca_f, oauth_f, risky, args.output)


if __name__ == "__main__":
    main()
Keep exploring