identity-access-management

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Identity & Access Management for Startups

创业公司身份与访问管理

Centralized identity management is not optional once your team exceeds a handful of people. This skill covers practical, production-ready configurations for SSO, SCIM provisioning, MFA enforcement, and access governance using the three most common identity providers for startups: Google Workspace, Okta, and Azure AD (Entra ID).

当团队规模超过几个人后,集中式身份管理就不再是可选项。本指南涵盖了针对创业公司最常用的三个身份提供商——Google Workspace、Okta和Azure AD(Entra ID)的SSO、SCIM配置、MFA强制实施以及访问治理的实用、可用于生产环境的配置方案。

1. When to Use This Skill

1. 适用场景

Reach for this skill when:
  • First SSO setup -- You are moving from individual app logins to centralized authentication.
  • Compliance audit preparation -- SOC 2, ISO 27001, or HIPAA requires documented access controls, MFA enforcement, and audit logs.
  • Team growth inflection -- You are crossing 15-20 employees and manual onboarding/offboarding is becoming error-prone.
  • Vendor security questionnaires -- Customers are asking about your identity posture and you need to demonstrate controls.
  • Incident response -- You need to revoke access quickly across all systems for a departing or compromised user.
Signs you are overdue:
  • Shared passwords in a spreadsheet or chat channel.
  • No central audit log of who accessed what and when.
  • Offboarding takes more than one business day.
  • Developers have standing admin access to production.

在以下场景中可使用本指南:
  • 首次SSO部署——正从单个应用独立登录转向集中式身份认证。
  • 合规审计准备——SOC 2、ISO 27001或HIPAA要求具备可追溯的访问控制、MFA强制实施机制和审计日志。
  • 团队规模扩张——员工数量达到15-20人,手动入职/离职流程开始频繁出错。
  • 供应商安全问卷——客户询问你的身份安全状况,需要展示相关控制措施。
  • 事件响应——需要快速撤销离职或账号泄露用户在所有系统中的访问权限。
以下信号表明你亟需实施集中式身份管理:
  • 在电子表格或聊天频道中共享密码。
  • 没有记录谁在何时访问了什么内容的集中审计日志。
  • 离职流程耗时超过一个工作日。
  • 开发人员长期拥有生产环境的管理员权限。

2. Google Workspace as Identity Provider

2. 以Google Workspace作为身份提供商

Google Workspace is the most common starting IdP for startups. Combined with the GAM CLI tool, it provides powerful automation.
Google Workspace是创业公司最常用的初始身份提供商。结合GAM CLI工具,它能提供强大的自动化能力。

Install GAM (Google Apps Manager)

安装GAM(Google Apps Manager)

bash
undefined
bash
undefined

Install GAM on Linux/macOS

Install GAM on Linux/macOS

Authorize GAM with your Workspace domain

Authorize GAM with your Workspace domain

gam oauth create
gam oauth create

Verify connection

Verify connection

gam info domain
undefined
gam info domain
undefined

Create Organizational Units

创建组织单元

Organizational units (OUs) control policy inheritance and app access.
bash
undefined
组织单元(OU)用于控制策略继承和应用访问权限。
bash
undefined

Create OUs for team structure

Create OUs for team structure

gam create org "Engineering" gam create org "Engineering/Backend" gam create org "Engineering/Frontend" gam create org "Operations" gam create org "Operations/IT" gam create org "Finance" gam create org "Contractors"
gam create org "Engineering" gam create org "Engineering/Backend" gam create org "Engineering/Frontend" gam create org "Operations" gam create org "Operations/IT" gam create org "Finance" gam create org "Contractors"

Move a user into an OU

Move a user into an OU

gam update user alice@company.com org "Engineering/Backend"
gam update user alice@company.com org "Engineering/Backend"

List all OUs

List all OUs

gam print orgs
undefined
gam print orgs
undefined

Configure a SAML App in Google Workspace

在Google Workspace中配置SAML应用

bash
undefined
bash
undefined

Export the Google IdP metadata (download from Admin Console or use GAM)

Export the Google IdP metadata (download from Admin Console or use GAM)

Admin Console: Apps > Web and mobile apps > Add app > Search for app > Download IdP metadata

Admin Console: Apps > Web and mobile apps > Add app > Search for app > Download IdP metadata

For a custom SAML app, you need:

For a custom SAML app, you need:

1. ACS URL (from the service provider)

1. ACS URL (from the service provider)

2. Entity ID (from the service provider)

2. Entity ID (from the service provider)

3. Name ID format (usually EMAIL)

3. Name ID format (usually EMAIL)

Example: Add a custom SAML app via Admin Console API

Example: Add a custom SAML app via Admin Console API

gam create samlapp "Internal Dashboard"
acs_url "https://dashboard.company.com/saml/acs"
entity_id "https://dashboard.company.com"
name_id_format "EMAIL"
name_id "user.primaryEmail"
gam create samlapp "Internal Dashboard"
acs_url "https://dashboard.company.com/saml/acs"
entity_id "https://dashboard.company.com"
name_id_format "EMAIL"
name_id "user.primaryEmail"

Assign the app to an OU

Assign the app to an OU

gam update samlapp "Internal Dashboard" org "Engineering" enabled on
gam update samlapp "Internal Dashboard" org "Engineering" enabled on

Verify SAML app status

Verify SAML app status

gam print samlappinfo "Internal Dashboard"
undefined
gam print samlappinfo "Internal Dashboard"
undefined

SCIM Provisioning with Google Workspace

Google Workspace的SCIM配置

bash
undefined
bash
undefined

Enable auto-provisioning for supported apps

Enable auto-provisioning for supported apps

Google Workspace supports automatic user provisioning for apps like:

Google Workspace supports automatic user provisioning for apps like:

Slack, Zoom, Box, Dropbox, Asana, GitHub Enterprise

Slack, Zoom, Box, Dropbox, Asana, GitHub Enterprise

List provisioned apps

List provisioned apps

gam print tokens
gam print tokens

Force sync provisioning for an app

Force sync provisioning for an app

gam sync samlapp "Slack" users
gam sync samlapp "Slack" users

Bulk create users from CSV

Bulk create users from CSV

users.csv format: firstname,lastname,email,org,password

users.csv format: firstname,lastname,email,org,password

gam csv users.csv gam create user ~email
firstname ~firstname lastname ~lastname
password ~password org ~org
changepassword on
undefined
gam csv users.csv gam create user ~email
firstname ~firstname lastname ~lastname
password ~password org ~org
changepassword on
undefined

Enforce MFA at the Workspace Level

在Workspace层面强制实施MFA

bash
undefined
bash
undefined

Enforce 2-step verification for the entire domain

Enforce 2-step verification for the entire domain

gam update org "/" 2sv enforced
gam update org "/" 2sv enforced

Enforce 2SV for a specific OU

Enforce 2SV for a specific OU

gam update org "Engineering" 2sv enforced
gam update org "Engineering" 2sv enforced

Set enforcement date (give users time to enroll)

Set enforcement date (give users time to enroll)

gam update org "/" 2sv enforced enforceddate 2026-04-15
gam update org "/" 2sv enforced enforceddate 2026-04-15

Check 2SV enrollment status for all users

Check 2SV enrollment status for all users

gam print users fields isEnforcedIn2Sv,isEnrolledIn2Sv
gam print users fields isEnforcedIn2Sv,isEnrolledIn2Sv

Find users who have NOT enrolled in 2SV

Find users who have NOT enrolled in 2SV

gam print users query "isEnrolledIn2Sv=false" fields primaryEmail,name

---
gam print users query "isEnrolledIn2Sv=false" fields primaryEmail,name

---

3. Okta Setup

3. Okta配置

Okta offers a free tier for startups (Okta for Startups program -- up to 100 users) making it an excellent choice for teams that need a dedicated IdP.
Okta为创业公司提供免费套餐(Okta for Startups计划——最多支持100名用户),对于需要专用身份提供商的团队来说是绝佳选择。

Initial Okta Configuration via API

通过API进行初始Okta配置

bash
undefined
bash
undefined

Set your Okta domain and API token

Set your Okta domain and API token

export OKTA_ORG_URL="https://company.okta.com" export OKTA_API_TOKEN="your-api-token"
export OKTA_ORG_URL="https://company.okta.com" export OKTA_API_TOKEN="your-api-token"

Verify connectivity

Verify connectivity

curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/org" | jq '.companyName'
curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/org" | jq '.companyName'

Create a user

Create a user

curl -s -X POST
-H "Authorization: SSWS ${OKTA_API_TOKEN}"
-H "Content-Type: application/json"
"${OKTA_ORG_URL}/api/v1/users?activate=true"
-d '{ "profile": { "firstName": "Alice", "lastName": "Engineer", "email": "alice@company.com", "login": "alice@company.com" }, "credentials": { "password": { "value": "TempP@ss123!" } } }' | jq '.id'
undefined
curl -s -X POST
-H "Authorization: SSWS ${OKTA_API_TOKEN}"
-H "Content-Type: application/json"
"${OKTA_ORG_URL}/api/v1/users?activate=true"
-d '{ "profile": { "firstName": "Alice", "lastName": "Engineer", "email": "alice@company.com", "login": "alice@company.com" }, "credentials": { "password": { "value": "TempP@ss123!" } } }' | jq '.id'
undefined

Create Groups for RBAC

创建用于RBAC的群组

bash
undefined
bash
undefined

Create groups

Create groups

for group in "Engineering" "Operations" "Finance" "Contractors" "AdminAccess"; do curl -s -X POST
-H "Authorization: SSWS ${OKTA_API_TOKEN}"
-H "Content-Type: application/json"
"${OKTA_ORG_URL}/api/v1/groups"
-d "{"profile": {"name": "${group}", "description": "${group} team group"}}"
| jq '{id: .id, name: .profile.name}' done
for group in "Engineering" "Operations" "Finance" "Contractors" "AdminAccess"; do curl -s -X POST
-H "Authorization: SSWS ${OKTA_API_TOKEN}"
-H "Content-Type: application/json"
"${OKTA_ORG_URL}/api/v1/groups"
-d "{"profile": {"name": "${group}", "description": "${group} team group"}}"
| jq '{id: .id, name: .profile.name}' done

Add user to group

Add user to group

USER_ID="00u1abc123" GROUP_ID="00g1def456" curl -s -X PUT
-H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/groups/${GROUP_ID}/users/${USER_ID}"
undefined
USER_ID="00u1abc123" GROUP_ID="00g1def456" curl -s -X PUT
-H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/groups/${GROUP_ID}/users/${USER_ID}"
undefined

Add a SAML Application in Okta

在Okta中添加SAML应用

bash
undefined
bash
undefined

Create a SAML 2.0 application

Create a SAML 2.0 application

curl -s -X POST
-H "Authorization: SSWS ${OKTA_API_TOKEN}"
-H "Content-Type: application/json"
"${OKTA_ORG_URL}/api/v1/apps"
-d '{ "name": "custom_saml_app", "label": "Internal Dashboard", "signOnMode": "SAML_2_0", "settings": { "signOn": { "defaultRelayState": "", "ssoAcsUrl": "https://dashboard.company.com/saml/acs", "audience": "https://dashboard.company.com", "recipient": "https://dashboard.company.com/saml/acs", "destination": "https://dashboard.company.com/saml/acs", "subjectNameIdFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", "attributeStatements": [ { "type": "EXPRESSION", "name": "email", "namespace": "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", "values": ["user.email"] }, { "type": "EXPRESSION", "name": "groups", "namespace": "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", "values": ["getFilteredGroups({"00g1def456"}, "group.name", 50)"] } ] } } }' | jq '{id: .id, label: .label, status: .status}'
curl -s -X POST
-H "Authorization: SSWS ${OKTA_API_TOKEN}"
-H "Content-Type: application/json"
"${OKTA_ORG_URL}/api/v1/apps"
-d '{ "name": "custom_saml_app", "label": "Internal Dashboard", "signOnMode": "SAML_2_0", "settings": { "signOn": { "defaultRelayState": "", "ssoAcsUrl": "https://dashboard.company.com/saml/acs", "audience": "https://dashboard.company.com", "recipient": "https://dashboard.company.com/saml/acs", "destination": "https://dashboard.company.com/saml/acs", "subjectNameIdFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", "attributeStatements": [ { "type": "EXPRESSION", "name": "email", "namespace": "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", "values": ["user.email"] }, { "type": "EXPRESSION", "name": "groups", "namespace": "urn:oasis:names:tc:SAML:2.0:attrname-format:basic", "values": ["getFilteredGroups({"00g1def456"}, "group.name", 50)"] } ] } } }' | jq '{id: .id, label: .label, status: .status}'

Assign group to application

Assign group to application

APP_ID="0oa1xyz789" curl -s -X PUT
-H "Authorization: SSWS ${OKTA_API_TOKEN}"
-H "Content-Type: application/json"
"${OKTA_ORG_URL}/api/v1/apps/${APP_ID}/groups/${GROUP_ID}"
undefined
APP_ID="0oa1xyz789" curl -s -X PUT
-H "Authorization: SSWS ${OKTA_API_TOKEN}"
-H "Content-Type: application/json"
"${OKTA_ORG_URL}/api/v1/apps/${APP_ID}/groups/${GROUP_ID}"
undefined

Okta MFA Policy

Okta MFA策略

bash
undefined
bash
undefined

Create an MFA enrollment policy requiring WebAuthn + TOTP

Create an MFA enrollment policy requiring WebAuthn + TOTP

curl -s -X POST
-H "Authorization: SSWS ${OKTA_API_TOKEN}"
-H "Content-Type: application/json"
"${OKTA_ORG_URL}/api/v1/policies"
-d '{ "type": "MFA_ENROLL", "name": "Require Strong MFA", "status": "ACTIVE", "settings": { "factors": { "webauthn": { "enroll": { "self": "REQUIRED" } }, "google_otp": { "enroll": { "self": "OPTIONAL" } }, "okta_email": { "enroll": { "self": "NOT_ALLOWED" } }, "okta_sms": { "enroll": { "self": "NOT_ALLOWED" } } } } }' | jq '{id: .id, name: .name, status: .status}'

---
curl -s -X POST
-H "Authorization: SSWS ${OKTA_API_TOKEN}"
-H "Content-Type: application/json"
"${OKTA_ORG_URL}/api/v1/policies"
-d '{ "type": "MFA_ENROLL", "name": "Require Strong MFA", "status": "ACTIVE", "settings": { "factors": { "webauthn": { "enroll": { "self": "REQUIRED" } }, "google_otp": { "enroll": { "self": "OPTIONAL" } }, "okta_email": { "enroll": { "self": "NOT_ALLOWED" } }, "okta_sms": { "enroll": { "self": "NOT_ALLOWED" } } } } }' | jq '{id: .id, name: .name, status: .status}'

---

4. Azure AD / Entra ID

4. Azure AD / Entra ID

Azure AD (now Microsoft Entra ID) is common at startups using Microsoft 365 or Azure cloud.
Azure AD(现名为Microsoft Entra ID)在使用Microsoft 365或Azure云的创业公司中较为常见。

Azure CLI Setup

Azure CLI配置

bash
undefined
bash
undefined

Install Azure CLI and sign in

Install Azure CLI and sign in

az login
az login

Set the default tenant

Set the default tenant

az account set --subscription "your-subscription-id"
az account set --subscription "your-subscription-id"

Verify tenant

Verify tenant

az ad signed-in-user show --query '{name:displayName, email:userPrincipalName}'
undefined
az ad signed-in-user show --query '{name:displayName, email:userPrincipalName}'
undefined

Create Users and Groups

创建用户和群组

bash
undefined
bash
undefined

Create a user

Create a user

az ad user create
--display-name "Alice Engineer"
--user-principal-name "alice@company.onmicrosoft.com"
--password "TempP@ss123!"
--force-change-password-next-sign-in true
az ad user create
--display-name "Alice Engineer"
--user-principal-name "alice@company.onmicrosoft.com"
--password "TempP@ss123!"
--force-change-password-next-sign-in true

Create security groups

Create security groups

for group in "SG-Engineering" "SG-Operations" "SG-Finance" "SG-Admins"; do az ad group create --display-name "$group" --mail-nickname "$group" done
for group in "SG-Engineering" "SG-Operations" "SG-Finance" "SG-Admins"; do az ad group create --display-name "$group" --mail-nickname "$group" done

Add user to group

Add user to group

USER_OID=$(az ad user show --id "alice@company.onmicrosoft.com" --query id -o tsv) GROUP_OID=$(az ad group show --group "SG-Engineering" --query id -o tsv) az ad group member add --group "$GROUP_OID" --member-id "$USER_OID"
USER_OID=$(az ad user show --id "alice@company.onmicrosoft.com" --query id -o tsv) GROUP_OID=$(az ad group show --group "SG-Engineering" --query id -o tsv) az ad group member add --group "$GROUP_OID" --member-id "$USER_OID"

List group members

List group members

az ad group member list --group "SG-Engineering" --query '[].{name:displayName, email:userPrincipalName}' -o table
undefined
az ad group member list --group "SG-Engineering" --query '[].{name:displayName, email:userPrincipalName}' -o table
undefined

Conditional Access Policies via Graph API

通过Graph API配置条件访问策略

bash
undefined
bash
undefined

Require MFA for all users accessing cloud apps

Require MFA for all users accessing cloud apps

Uses Microsoft Graph API

Uses Microsoft Graph API

ACCESS_TOKEN=$(az account get-access-token --resource https://graph.microsoft.com --query accessToken -o tsv)
curl -s -X POST
-H "Authorization: Bearer ${ACCESS_TOKEN}"
-H "Content-Type: application/json"
"https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies"
-d '{ "displayName": "Require MFA for all users", "state": "enabledForReportingButNotEnforced", "conditions": { "users": { "includeUsers": ["All"], "excludeGroups": ["'${BREAKGLASS_GROUP_OID}'"] }, "applications": { "includeApplications": ["All"] } }, "grantControls": { "operator": "OR", "builtInControls": ["mfa"] } }'
ACCESS_TOKEN=$(az account get-access-token --resource https://graph.microsoft.com --query accessToken -o tsv)
curl -s -X POST
-H "Authorization: Bearer ${ACCESS_TOKEN}"
-H "Content-Type: application/json"
"https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies"
-d '{ "displayName": "Require MFA for all users", "state": "enabledForReportingButNotEnforced", "conditions": { "users": { "includeUsers": ["All"], "excludeGroups": ["'${BREAKGLASS_GROUP_OID}'"] }, "applications": { "includeApplications": ["All"] } }, "grantControls": { "operator": "OR", "builtInControls": ["mfa"] } }'

Block legacy authentication (critical for security)

Block legacy authentication (critical for security)

curl -s -X POST
-H "Authorization: Bearer ${ACCESS_TOKEN}"
-H "Content-Type: application/json"
"https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies"
-d '{ "displayName": "Block legacy authentication", "state": "enabled", "conditions": { "users": { "includeUsers": ["All"] }, "applications": { "includeApplications": ["All"] }, "clientAppTypes": ["exchangeActiveSync", "other"] }, "grantControls": { "operator": "OR", "builtInControls": ["block"] } }'

---
curl -s -X POST
-H "Authorization: Bearer ${ACCESS_TOKEN}"
-H "Content-Type: application/json"
"https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies"
-d '{ "displayName": "Block legacy authentication", "state": "enabled", "conditions": { "users": { "includeUsers": ["All"] }, "applications": { "includeApplications": ["All"] }, "clientAppTypes": ["exchangeActiveSync", "other"] }, "grantControls": { "operator": "OR", "builtInControls": ["block"] } }'

---

5. SSO Integration Patterns

5. SSO集成模式

SAML vs OIDC Decision Guide

SAML vs OIDC决策指南

FactorSAML 2.0OIDC / OAuth 2.0
Best forEnterprise SaaS appsSPAs, mobile apps, APIs
Token formatXML assertionsJWT tokens
Setup complexityHigher (certificates, metadata XML)Lower (client ID + secret)
LogoutInconsistent (SLO is poorly supported)Token expiry + revocation
Use whenApp only supports SAMLYou have a choice, or need API auth
Rule of thumb: If the SaaS vendor supports OIDC, prefer it. If they only support SAML, use SAML. Never use LDAP-over-internet.
因素SAML 2.0OIDC / OAuth 2.0
最佳适用场景企业级SaaS应用单页应用、移动应用、API
令牌格式XML断言JWT令牌
配置复杂度较高(证书、元数据XML)较低(客户端ID+密钥)
登出机制不一致(SLO支持度差)令牌过期+撤销
使用时机应用仅支持SAML有选择余地,或需要API认证
经验法则:如果SaaS供应商支持OIDC,优先选择OIDC;如果仅支持SAML,则使用SAML。绝对不要使用互联网环境下的LDAP。

Integrating Common SaaS Apps

常见SaaS应用集成

Slack Enterprise SSO

Slack Enterprise SSO

bash
undefined
bash
undefined

Okta OIDC integration for Slack

Okta OIDC integration for Slack

1. In Okta: Applications > Browse App Catalog > Slack

1. In Okta: Applications > Browse App Catalog > Slack

2. Configure with your Slack workspace URL

2. Configure with your Slack workspace URL

3. Enable SCIM provisioning

3. Enable SCIM provisioning

Verify Slack SCIM connection

Verify Slack SCIM connection

curl -s -H "Authorization: Bearer ${SLACK_SCIM_TOKEN}"
"https://api.slack.com/scim/v2/Users?count=5" | jq '.Resources[].userName'
undefined
curl -s -H "Authorization: Bearer ${SLACK_SCIM_TOKEN}"
"https://api.slack.com/scim/v2/Users?count=5" | jq '.Resources[].userName'
undefined

GitHub Organization SSO

GitHub组织SSO

bash
undefined
bash
undefined

Configure SAML for GitHub Org (requires GitHub Enterprise Cloud)

Configure SAML for GitHub Org (requires GitHub Enterprise Cloud)

1. GitHub Org Settings > Authentication security > Enable SAML

1. GitHub Org Settings > Authentication security > Enable SAML

2. Provide IdP SSO URL, IdP issuer, public certificate from your IdP

2. Provide IdP SSO URL, IdP issuer, public certificate from your IdP

Use GitHub CLI to verify SSO status

Use GitHub CLI to verify SSO status

gh api orgs/company/credential-authorizations --paginate
| jq '.[] | {login: .login, credential_type: .credential_type, authorized_at: .authorized_credential_note}'
gh api orgs/company/credential-authorizations --paginate
| jq '.[] | {login: .login, credential_type: .credential_type, authorized_at: .authorized_credential_note}'

Require SAML SSO for all org members

Require SAML SSO for all org members

gh api -X PATCH orgs/company
-f saml_enforced=true
undefined
gh api -X PATCH orgs/company
-f saml_enforced=true
undefined

AWS SSO (IAM Identity Center)

AWS SSO(IAM Identity Center)

bash
undefined
bash
undefined

Configure AWS IAM Identity Center with external IdP

Configure AWS IAM Identity Center with external IdP

aws sso-admin list-instances --query 'Instances[0].InstanceArn' --output text
INSTANCE_ARN="arn:aws:sso:::instance/ssoins-1234567890" IDENTITY_STORE_ID="d-1234567890"
aws sso-admin list-instances --query 'Instances[0].InstanceArn' --output text
INSTANCE_ARN="arn:aws:sso:::instance/ssoins-1234567890" IDENTITY_STORE_ID="d-1234567890"

Create a permission set

Create a permission set

aws sso-admin create-permission-set
--instance-arn "$INSTANCE_ARN"
--name "DeveloperAccess"
--description "Read-only + deploy access for engineers"
--session-duration "PT8H"
aws sso-admin create-permission-set
--instance-arn "$INSTANCE_ARN"
--name "DeveloperAccess"
--description "Read-only + deploy access for engineers"
--session-duration "PT8H"

Attach AWS managed policy to permission set

Attach AWS managed policy to permission set

PERMISSION_SET_ARN="arn:aws:sso:::permissionSet/ssoins-1234567890/ps-abc123" aws sso-admin attach-managed-policy-to-permission-set
--instance-arn "$INSTANCE_ARN"
--permission-set-arn "$PERMISSION_SET_ARN"
--managed-policy-arn "arn:aws:iam::aws:policy/ReadOnlyAccess"
PERMISSION_SET_ARN="arn:aws:sso:::permissionSet/ssoins-1234567890/ps-abc123" aws sso-admin attach-managed-policy-to-permission-set
--instance-arn "$INSTANCE_ARN"
--permission-set-arn "$PERMISSION_SET_ARN"
--managed-policy-arn "arn:aws:iam::aws:policy/ReadOnlyAccess"

Assign group to AWS account with permission set

Assign group to AWS account with permission set

aws sso-admin create-account-assignment
--instance-arn "$INSTANCE_ARN"
--target-id "123456789012"
--target-type AWS_ACCOUNT
--permission-set-arn "$PERMISSION_SET_ARN"
--principal-type GROUP
--principal-id "a1b2c3d4-5678-90ab-cdef-GROUP001"

---
aws sso-admin create-account-assignment
--instance-arn "$INSTANCE_ARN"
--target-id "123456789012"
--target-type AWS_ACCOUNT
--permission-set-arn "$PERMISSION_SET_ARN"
--principal-type GROUP
--principal-id "a1b2c3d4-5678-90ab-cdef-GROUP001"

---

6. SCIM Provisioning

6. SCIM配置

SCIM (System for Cross-domain Identity Management) automates user lifecycle across SaaS apps.
SCIM(跨域身份管理系统)可自动化SaaS应用中的用户生命周期管理。

SCIM API Examples

SCIM API示例

bash
undefined
bash
undefined

Standard SCIM 2.0 endpoints (most IdPs and SaaS apps follow this)

Standard SCIM 2.0 endpoints (most IdPs and SaaS apps follow this)

SCIM_BASE="https://app.example.com/scim/v2" SCIM_TOKEN="your-scim-bearer-token"
SCIM_BASE="https://app.example.com/scim/v2" SCIM_TOKEN="your-scim-bearer-token"

List users

List users

curl -s -H "Authorization: Bearer ${SCIM_TOKEN}"
"${SCIM_BASE}/Users?count=10&startIndex=1" | jq '.Resources[] | {id, userName, active}'
curl -s -H "Authorization: Bearer ${SCIM_TOKEN}"
"${SCIM_BASE}/Users?count=10&startIndex=1" | jq '.Resources[] | {id, userName, active}'

Create a user via SCIM

Create a user via SCIM

curl -s -X POST
-H "Authorization: Bearer ${SCIM_TOKEN}"
-H "Content-Type: application/scim+json"
"${SCIM_BASE}/Users"
-d '{ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], "userName": "alice@company.com", "name": { "givenName": "Alice", "familyName": "Engineer" }, "emails": [{ "primary": true, "value": "alice@company.com", "type": "work" }], "active": true, "groups": [] }' | jq '{id, userName, active}'
curl -s -X POST
-H "Authorization: Bearer ${SCIM_TOKEN}"
-H "Content-Type: application/scim+json"
"${SCIM_BASE}/Users"
-d '{ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], "userName": "alice@company.com", "name": { "givenName": "Alice", "familyName": "Engineer" }, "emails": [{ "primary": true, "value": "alice@company.com", "type": "work" }], "active": true, "groups": [] }' | jq '{id, userName, active}'

Deactivate a user via SCIM (PATCH is the standard for partial updates)

Deactivate a user via SCIM (PATCH is the standard for partial updates)

USER_SCIM_ID="abc-123-def" curl -s -X PATCH
-H "Authorization: Bearer ${SCIM_TOKEN}"
-H "Content-Type: application/scim+json"
"${SCIM_BASE}/Users/${USER_SCIM_ID}"
-d '{ "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations": [{ "op": "replace", "value": { "active": false } }] }' | jq '{id, userName, active}'
USER_SCIM_ID="abc-123-def" curl -s -X PATCH
-H "Authorization: Bearer ${SCIM_TOKEN}"
-H "Content-Type: application/scim+json"
"${SCIM_BASE}/Users/${USER_SCIM_ID}"
-d '{ "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations": [{ "op": "replace", "value": { "active": false } }] }' | jq '{id, userName, active}'

Delete a user permanently via SCIM

Delete a user permanently via SCIM

curl -s -X DELETE
-H "Authorization: Bearer ${SCIM_TOKEN}"
"${SCIM_BASE}/Users/${USER_SCIM_ID}"
undefined
curl -s -X DELETE
-H "Authorization: Bearer ${SCIM_TOKEN}"
"${SCIM_BASE}/Users/${USER_SCIM_ID}"
undefined

SCIM Group Management

SCIM群组管理

bash
undefined
bash
undefined

Create a group via SCIM

Create a group via SCIM

curl -s -X POST
-H "Authorization: Bearer ${SCIM_TOKEN}"
-H "Content-Type: application/scim+json"
"${SCIM_BASE}/Groups"
-d '{ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"], "displayName": "Engineering", "members": [ { "value": "user-id-001", "display": "alice@company.com" }, { "value": "user-id-002", "display": "bob@company.com" } ] }' | jq '{id, displayName}'
curl -s -X POST
-H "Authorization: Bearer ${SCIM_TOKEN}"
-H "Content-Type: application/scim+json"
"${SCIM_BASE}/Groups"
-d '{ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"], "displayName": "Engineering", "members": [ { "value": "user-id-001", "display": "alice@company.com" }, { "value": "user-id-002", "display": "bob@company.com" } ] }' | jq '{id, displayName}'

Add a member to an existing group

Add a member to an existing group

GROUP_SCIM_ID="grp-456" curl -s -X PATCH
-H "Authorization: Bearer ${SCIM_TOKEN}"
-H "Content-Type: application/scim+json"
"${SCIM_BASE}/Groups/${GROUP_SCIM_ID}"
-d '{ "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations": [{ "op": "add", "path": "members", "value": [{ "value": "user-id-003" }] }] }'

---
GROUP_SCIM_ID="grp-456" curl -s -X PATCH
-H "Authorization: Bearer ${SCIM_TOKEN}"
-H "Content-Type: application/scim+json"
"${SCIM_BASE}/Groups/${GROUP_SCIM_ID}"
-d '{ "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations": [{ "op": "add", "path": "members", "value": [{ "value": "user-id-003" }] }] }'

---

7. MFA Enforcement

7. MFA强制实施

WebAuthn / Passkeys (Strongest)

WebAuthn / Passkeys(最安全)

WebAuthn (FIDO2) hardware keys and passkeys are phishing-resistant and should be the primary MFA factor.
bash
undefined
WebAuthn(FIDO2)硬件密钥和Passkeys具备防钓鱼能力,应作为主要的MFA验证方式。
bash
undefined

Okta: Enforce WebAuthn as primary factor

Okta: Enforce WebAuthn as primary factor

curl -s -X PUT
-H "Authorization: SSWS ${OKTA_API_TOKEN}"
-H "Content-Type: application/json"
"${OKTA_ORG_URL}/api/v1/org/factors/webauthn"
-d '{ "status": "ACTIVE" }'
curl -s -X PUT
-H "Authorization: SSWS ${OKTA_API_TOKEN}"
-H "Content-Type: application/json"
"${OKTA_ORG_URL}/api/v1/org/factors/webauthn"
-d '{ "status": "ACTIVE" }'

Google Workspace: Enforce security keys only (disable SMS/voice)

Google Workspace: Enforce security keys only (disable SMS/voice)

gam update org "/" 2sv enforced allowedmethods security_key
gam update org "/" 2sv enforced allowedmethods security_key

Azure AD: Require phishing-resistant MFA via conditional access

Azure AD: Require phishing-resistant MFA via conditional access

(use the Graph API conditional access endpoint with authenticationStrengths)

(use the Graph API conditional access endpoint with authenticationStrengths)

curl -s -X POST
-H "Authorization: Bearer ${ACCESS_TOKEN}"
-H "Content-Type: application/json"
"https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies"
-d '{ "displayName": "Require phishing-resistant MFA for admins", "state": "enabled", "conditions": { "users": { "includeRoles": ["62e90394-69f5-4237-9190-012177145e10"] }, "applications": { "includeApplications": ["All"] } }, "grantControls": { "operator": "OR", "authenticationStrength": { "id": "00000000-0000-0000-0000-000000000004" } } }'
undefined
curl -s -X POST
-H "Authorization: Bearer ${ACCESS_TOKEN}"
-H "Content-Type: application/json"
"https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies"
-d '{ "displayName": "Require phishing-resistant MFA for admins", "state": "enabled", "conditions": { "users": { "includeRoles": ["62e90394-69f5-4237-9190-012177145e10"] }, "applications": { "includeApplications": ["All"] } }, "grantControls": { "operator": "OR", "authenticationStrength": { "id": "00000000-0000-0000-0000-000000000004" } } }'
undefined

TOTP Backup Configuration

TOTP备份配置

bash
undefined
bash
undefined

Generate backup codes for users (Okta)

Generate backup codes for users (Okta)

USER_ID="00u1abc123" curl -s -X POST
-H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/users/${USER_ID}/factors"
-d '{ "factorType": "token:software:totp", "provider": "GOOGLE" }' | jq '{id: .id, status: .status}'
undefined
USER_ID="00u1abc123" curl -s -X POST
-H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/users/${USER_ID}/factors"
-d '{ "factorType": "token:software:totp", "provider": "GOOGLE" }' | jq '{id: .id, status: .status}'
undefined

MFA Bypass Procedure (Emergency)

MFA绕过流程(紧急情况)

bash
undefined
bash
undefined

Okta: Reset MFA for a locked-out user

Okta: Reset MFA for a locked-out user

USER_ID="00u1abc123"
USER_ID="00u1abc123"

List enrolled factors

List enrolled factors

curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/users/${USER_ID}/factors" | jq '.[].factorType'
curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/users/${USER_ID}/factors" | jq '.[].factorType'

Delete a specific factor to allow re-enrollment

Delete a specific factor to allow re-enrollment

FACTOR_ID="fct1abc123" curl -s -X DELETE
-H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/users/${USER_ID}/factors/${FACTOR_ID}"
FACTOR_ID="fct1abc123" curl -s -X DELETE
-H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/users/${USER_ID}/factors/${FACTOR_ID}"

Google Workspace: Generate backup verification codes

Google Workspace: Generate backup verification codes

gam user alice@company.com update backupcodes
gam user alice@company.com update backupcodes

Azure AD: Require re-registration of MFA methods

Azure AD: Require re-registration of MFA methods

8. Role-Based Access Control

8. 基于角色的访问控制(RBAC)

Group-Based Access Patterns

基于群组的访问模式

Map every application permission to a group, never to an individual user.
bash
undefined
将所有应用权限映射到群组,而非单个用户。
bash
undefined

Naming convention: APP-ROLE

Naming convention: APP-ROLE

Examples:

Examples:

aws-developer -> AWS ReadOnly + deploy

aws-developer -> AWS ReadOnly + deploy

aws-admin -> AWS AdministratorAccess

aws-admin -> AWS AdministratorAccess

github-engineer -> GitHub write access

github-engineer -> GitHub write access

github-admin -> GitHub admin access

github-admin -> GitHub admin access

slack-member -> Slack standard member

slack-member -> Slack standard member

pagerduty-oncall -> PagerDuty responder role

pagerduty-oncall -> PagerDuty responder role

Okta: Create group rules for automatic assignment based on department

Okta: Create group rules for automatic assignment based on department

curl -s -X POST
-H "Authorization: SSWS ${OKTA_API_TOKEN}"
-H "Content-Type: application/json"
"${OKTA_ORG_URL}/api/v1/groups/rules"
-d '{ "type": "group_rule", "name": "Auto-assign engineers to GitHub", "conditions": { "expression": { "value": "user.department == "Engineering"", "type": "urn:okta:expression:1.0" } }, "actions": { "assignUserToGroups": { "groupIds": ["GITHUB_ENGINEERS_GROUP_ID"] } } }'
undefined
curl -s -X POST
-H "Authorization: SSWS ${OKTA_API_TOKEN}"
-H "Content-Type: application/json"
"${OKTA_ORG_URL}/api/v1/groups/rules"
-d '{ "type": "group_rule", "name": "Auto-assign engineers to GitHub", "conditions": { "expression": { "value": "user.department == "Engineering"", "type": "urn:okta:expression:1.0" } }, "actions": { "assignUserToGroups": { "groupIds": ["GITHUB_ENGINEERS_GROUP_ID"] } } }'
undefined

Just-in-Time (JIT) Access

即时访问(JIT)

bash
undefined
bash
undefined

AWS: Grant temporary elevated access using STS assume-role

AWS: Grant temporary elevated access using STS assume-role

The user assumes a role that expires after a set duration

The user assumes a role that expires after a set duration

aws sts assume-role
--role-arn "arn:aws:iam::123456789012:role/EmergencyAdmin"
--role-session-name "alice-incident-2026-03-24"
--duration-seconds 3600
| jq '{AccessKeyId: .Credentials.AccessKeyId, Expiration: .Credentials.Expiration}'
aws sts assume-role
--role-arn "arn:aws:iam::123456789012:role/EmergencyAdmin"
--role-session-name "alice-incident-2026-03-24"
--duration-seconds 3600
| jq '{AccessKeyId: .Credentials.AccessKeyId, Expiration: .Credentials.Expiration}'

Okta: Create a time-limited group membership (via API scheduled task)

Okta: Create a time-limited group membership (via API scheduled task)

Add user to admin group

Add user to admin group

curl -s -X PUT
-H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/groups/${ADMIN_GROUP_ID}/users/${USER_ID}"
curl -s -X PUT
-H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/groups/${ADMIN_GROUP_ID}/users/${USER_ID}"

Schedule removal after 4 hours (use a cron job or automation tool)

Schedule removal after 4 hours (use a cron job or automation tool)

echo "0 */4 * * * curl -s -X DELETE -H 'Authorization: SSWS ${OKTA_API_TOKEN}'
'${OKTA_ORG_URL}/api/v1/groups/${ADMIN_GROUP_ID}/users/${USER_ID}'" | crontab -
undefined
echo "0 */4 * * * curl -s -X DELETE -H 'Authorization: SSWS ${OKTA_API_TOKEN}'
'${OKTA_ORG_URL}/api/v1/groups/${ADMIN_GROUP_ID}/users/${USER_ID}'" | crontab -
undefined

Break-Glass Accounts

应急账号

bash
undefined
bash
undefined

Create break-glass accounts that bypass SSO/MFA for emergency access

Create break-glass accounts that bypass SSO/MFA for emergency access

These accounts must be:

These accounts must be:

1. Excluded from conditional access / MFA policies

1. Excluded from conditional access / MFA policies

2. Protected with extremely long passwords stored in a physical safe

2. Protected with extremely long passwords stored in a physical safe

3. Monitored with alerts on any usage

3. Monitored with alerts on any usage

Azure AD: Create break-glass account

Azure AD: Create break-glass account

az ad user create
--display-name "Break Glass 1"
--user-principal-name "breakglass1@company.onmicrosoft.com"
--password "$(openssl rand -base64 48)"
--force-change-password-next-sign-in false
az ad user create
--display-name "Break Glass 1"
--user-principal-name "breakglass1@company.onmicrosoft.com"
--password "$(openssl rand -base64 48)"
--force-change-password-next-sign-in false

Assign Global Administrator role

Assign Global Administrator role

az ad group member add --group "SG-BreakGlass" --member-id "$BREAKGLASS_OID"
az ad group member add --group "SG-BreakGlass" --member-id "$BREAKGLASS_OID"

Set up alert on break-glass sign-in (Azure Monitor)

Set up alert on break-glass sign-in (Azure Monitor)

az monitor activity-log alert create
--name "BreakGlass-SignIn-Alert"
--resource-group "security-rg"
--condition category=Administrative and caller=breakglass1@company.onmicrosoft.com
--action-group "/subscriptions/SUB_ID/resourceGroups/security-rg/providers/microsoft.insights/actionGroups/SecurityTeam"

---
az monitor activity-log alert create
--name "BreakGlass-SignIn-Alert"
--resource-group "security-rg"
--condition category=Administrative and caller=breakglass1@company.onmicrosoft.com
--action-group "/subscriptions/SUB_ID/resourceGroups/security-rg/providers/microsoft.insights/actionGroups/SecurityTeam"

---

9. Audit & Compliance

9. 审计与合规

Login Audit Logs

登录审计日志

bash
undefined
bash
undefined

Google Workspace: Pull login audit logs

Google Workspace: Pull login audit logs

gam report login user all start "2026-03-01" end "2026-03-24"
fields "actorEmail,ipAddress,loginType,isSecondFactor,isSuspicious"
gam report login user all start "2026-03-01" end "2026-03-24"
fields "actorEmail,ipAddress,loginType,isSecondFactor,isSuspicious"

Okta: Query system log for authentication events

Okta: Query system log for authentication events

curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/logs?filter=eventType+eq+"user.session.start"&since=2026-03-01T00:00:00Z&limit=100"
| jq '.[] | {actor: .actor.displayName, time: .published, outcome: .outcome.result, ip: .client.ipAddress}'
curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/logs?filter=eventType+eq+"user.session.start"&since=2026-03-01T00:00:00Z&limit=100"
| jq '.[] | {actor: .actor.displayName, time: .published, outcome: .outcome.result, ip: .client.ipAddress}'

Azure AD: Pull sign-in logs via Graph API

Azure AD: Pull sign-in logs via Graph API

curl -s -H "Authorization: Bearer ${ACCESS_TOKEN}"
"https://graph.microsoft.com/v1.0/auditLogs/signIns?\$filter=createdDateTime ge 2026-03-01T00:00:00Z&$top=50"
| jq '.value[] | {user: .userDisplayName, app: .appDisplayName, status: .status.errorCode, ip: .ipAddress, mfa: .mfaDetail}'
undefined
curl -s -H "Authorization: Bearer ${ACCESS_TOKEN}"
"https://graph.microsoft.com/v1.0/auditLogs/signIns?\$filter=createdDateTime ge 2026-03-01T00:00:00Z&$top=50"
| jq '.value[] | {user: .userDisplayName, app: .appDisplayName, status: .status.errorCode, ip: .ipAddress, mfa: .mfaDetail}'
undefined

Access Reviews

访问审核

bash
undefined
bash
undefined

List all users and their group memberships for quarterly access review

List all users and their group memberships for quarterly access review

Google Workspace

Google Workspace

gam print group-members fields email,role > /tmp/access-review-groups.csv
gam print group-members fields email,role > /tmp/access-review-groups.csv

Okta: Export all users with their app assignments

Okta: Export all users with their app assignments

curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/users?limit=200"
| jq -r '.[] | [.profile.email, .status, .lastLogin] | @csv' > /tmp/okta-users.csv
curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/users?limit=200"
| jq -r '.[] | [.profile.email, .status, .lastLogin] | @csv' > /tmp/okta-users.csv

For each user, list their app assignments

For each user, list their app assignments

while IFS= read -r user_id; do curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/users/${user_id}/appLinks"
| jq -r '.[] | [.label, .linkUrl] | @csv' done < /tmp/okta-user-ids.txt > /tmp/okta-access-review.csv
while IFS= read -r user_id; do curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/users/${user_id}/appLinks"
| jq -r '.[] | [.label, .linkUrl] | @csv' done < /tmp/okta-user-ids.txt > /tmp/okta-access-review.csv

Azure AD: List role assignments

Azure AD: List role assignments

az role assignment list --all --query '[].{principal:principalName, role:roleDefinitionName, scope:scope}' -o table
undefined
az role assignment list --all --query '[].{principal:principalName, role:roleDefinitionName, scope:scope}' -o table
undefined

Compliance Reporting

合规报告

bash
undefined
bash
undefined

Count of users with/without MFA enrolled

Count of users with/without MFA enrolled

Google Workspace

Google Workspace

echo "=== MFA Enrollment Report ===" echo "Enrolled:" gam print users fields isEnrolledIn2Sv | grep -c True echo "Not enrolled:" gam print users fields isEnrolledIn2Sv | grep -c False
echo "=== MFA Enrollment Report ===" echo "Enrolled:" gam print users fields isEnrolledIn2Sv | grep -c True echo "Not enrolled:" gam print users fields isEnrolledIn2Sv | grep -c False

Okta: Users without any MFA factor

Okta: Users without any MFA factor

curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/users?filter=status+eq+"ACTIVE"&limit=200"
| jq '[.[] | select(.credentials.provider.type != "SOCIAL") | .id] | length'
curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/users?filter=status+eq+"ACTIVE"&limit=200"
| jq '[.[] | select(.credentials.provider.type != "SOCIAL") | .id] | length'

Check for stale accounts (no login in 90 days)

Check for stale accounts (no login in 90 days)

NINETY_DAYS_AGO=$(date -d "-90 days" +%Y-%m-%dT00:00:00Z 2>/dev/null || date -v-90d +%Y-%m-%dT00:00:00Z) curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/users?filter=lastLogin+lt+"${NINETY_DAYS_AGO}"&limit=200"
| jq '.[] | {email: .profile.email, lastLogin: .lastLogin}'

---
NINETY_DAYS_AGO=$(date -d "-90 days" +%Y-%m-%dT00:00:00Z 2>/dev/null || date -v-90d +%Y-%m-%dT00:00:00Z) curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/users?filter=lastLogin+lt+"${NINETY_DAYS_AGO}"&limit=200"
| jq '.[] | {email: .profile.email, lastLogin: .lastLogin}'

---

10. Offboarding

10. 员工离职流程

Account Deactivation Checklist

账号停用 checklist

Run this sequence when an employee departs. Order matters -- revoke sessions first, then deactivate.
bash
DEPARTING_USER="alice@company.com"
员工离职时执行以下步骤,顺序至关重要——先撤销会话,再停用账号。
bash
DEPARTING_USER="alice@company.com"

Step 1: Revoke all active sessions immediately

Step 1: Revoke all active sessions immediately

Okta

Okta

USER_ID=$(curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/users/${DEPARTING_USER}" | jq -r '.id')
curl -s -X DELETE
-H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/users/${USER_ID}/sessions"
USER_ID=$(curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/users/${DEPARTING_USER}" | jq -r '.id')
curl -s -X DELETE
-H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/users/${USER_ID}/sessions"

Google Workspace: Revoke tokens and sign out

Google Workspace: Revoke tokens and sign out

gam user "${DEPARTING_USER}" signout gam user "${DEPARTING_USER}" deprovision
gam user "${DEPARTING_USER}" signout gam user "${DEPARTING_USER}" deprovision

Azure AD: Revoke all refresh tokens

Azure AD: Revoke all refresh tokens

az ad user update --id "${DEPARTING_USER}" --account-enabled false az rest --method POST
--url "https://graph.microsoft.com/v1.0/users/${DEPARTING_USER}/revokeSignInSessions"
az ad user update --id "${DEPARTING_USER}" --account-enabled false az rest --method POST
--url "https://graph.microsoft.com/v1.0/users/${DEPARTING_USER}/revokeSignInSessions"

Step 2: Deactivate the user account

Step 2: Deactivate the user account

Okta

Okta

curl -s -X POST
-H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/users/${USER_ID}/lifecycle/deactivate"
curl -s -X POST
-H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/users/${USER_ID}/lifecycle/deactivate"

Google Workspace

Google Workspace

gam update user "${DEPARTING_USER}" suspended on
gam update user "${DEPARTING_USER}" suspended on

Step 3: Transfer data ownership

Step 3: Transfer data ownership

Google Workspace: Transfer Drive files

Google Workspace: Transfer Drive files

gam user "${DEPARTING_USER}" transfer drive manager@company.com
gam user "${DEPARTING_USER}" transfer drive manager@company.com

Google Workspace: Transfer Calendar ownership

Google Workspace: Transfer Calendar ownership

gam user "${DEPARTING_USER}" transfer calendar manager@company.com
gam user "${DEPARTING_USER}" transfer calendar manager@company.com

Step 4: Remove from all groups (prevents future provisioning)

Step 4: Remove from all groups (prevents future provisioning)

Okta

Okta

curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/users/${USER_ID}/groups"
| jq -r '.[].id' | while read gid; do curl -s -X DELETE
-H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/groups/${gid}/users/${USER_ID}" done
curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/users/${USER_ID}/groups"
| jq -r '.[].id' | while read gid; do curl -s -X DELETE
-H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/groups/${gid}/users/${USER_ID}" done

Step 5: Revoke app-specific tokens

Step 5: Revoke app-specific tokens

GitHub: Remove from org

GitHub: Remove from org

gh api -X DELETE "orgs/company/members/${DEPARTING_USER}"
gh api -X DELETE "orgs/company/members/${DEPARTING_USER}"

Slack: Deactivate via SCIM

Slack: Deactivate via SCIM

SLACK_USER_ID=$(curl -s -H "Authorization: Bearer ${SLACK_SCIM_TOKEN}"
"https://api.slack.com/scim/v2/Users?filter=userName+eq+\"${DEPARTING_USER}\""
| jq -r '.Resources[0].id')
curl -s -X PATCH
-H "Authorization: Bearer ${SLACK_SCIM_TOKEN}"
-H "Content-Type: application/scim+json"
"https://api.slack.com/scim/v2/Users/${SLACK_USER_ID}"
-d '{"schemas":["urn:ietf:params:scim:api:messages:2.0:PatchOp"],"Operations":[{"op":"replace","value":{"active":false}}]}'
SLACK_USER_ID=$(curl -s -H "Authorization: Bearer ${SLACK_SCIM_TOKEN}"
"https://api.slack.com/scim/v2/Users?filter=userName+eq+\"${DEPARTING_USER}\""
| jq -r '.Resources[0].id')
curl -s -X PATCH
-H "Authorization: Bearer ${SLACK_SCIM_TOKEN}"
-H "Content-Type: application/scim+json"
"https://api.slack.com/scim/v2/Users/${SLACK_USER_ID}"
-d '{"schemas":["urn:ietf:params:scim:api:messages:2.0:PatchOp"],"Operations":[{"op":"replace","value":{"active":false}}]}'

AWS: Remove SSO access

AWS: Remove SSO access

aws sso-admin delete-account-assignment
--instance-arn "$INSTANCE_ARN"
--target-id "123456789012"
--target-type AWS_ACCOUNT
--permission-set-arn "$PERMISSION_SET_ARN"
--principal-type USER
--principal-id "$AWS_SSO_USER_ID"
aws sso-admin delete-account-assignment
--instance-arn "$INSTANCE_ARN"
--target-id "123456789012"
--target-type AWS_ACCOUNT
--permission-set-arn "$PERMISSION_SET_ARN"
--principal-type USER
--principal-id "$AWS_SSO_USER_ID"

Step 6: Document and log

Step 6: Document and log

echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) | OFFBOARD | ${DEPARTING_USER} | all sessions revoked, account suspended, data transferred to manager@company.com" >> /var/log/offboarding-audit.log
undefined
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) | OFFBOARD | ${DEPARTING_USER} | all sessions revoked, account suspended, data transferred to manager@company.com" >> /var/log/offboarding-audit.log
undefined

Post-Offboarding Verification

离职后验证

bash
DEPARTING_USER="alice@company.com"
bash
DEPARTING_USER="alice@company.com"

Verify account is suspended/deactivated

Verify account is suspended/deactivated

echo "=== Offboarding Verification ==="
echo "=== Offboarding Verification ==="

Google Workspace

Google Workspace

gam info user "${DEPARTING_USER}" fields suspended | grep -i "suspended: true" && echo "[OK] Google suspended" || echo "[FAIL] Google still active"
gam info user "${DEPARTING_USER}" fields suspended | grep -i "suspended: true" && echo "[OK] Google suspended" || echo "[FAIL] Google still active"

Okta

Okta

curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/users/${DEPARTING_USER}"
| jq -r '.status' | grep -q "DEPROVISIONED" && echo "[OK] Okta deprovisioned" || echo "[FAIL] Okta still active"
curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/users/${DEPARTING_USER}"
| jq -r '.status' | grep -q "DEPROVISIONED" && echo "[OK] Okta deprovisioned" || echo "[FAIL] Okta still active"

GitHub

GitHub

gh api "orgs/company/members/${DEPARTING_USER}" 2>&1 | grep -q "404" && echo "[OK] GitHub removed" || echo "[FAIL] GitHub still member"
gh api "orgs/company/members/${DEPARTING_USER}" 2>&1 | grep -q "404" && echo "[OK] GitHub removed" || echo "[FAIL] GitHub still member"

Check for any remaining active sessions in audit logs

Check for any remaining active sessions in audit logs

echo "=== Checking for post-offboard activity ===" curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/logs?filter=actor.alternateId+eq+"${DEPARTING_USER}"&since=$(date -u +%Y-%m-%dT%H:%M:%SZ)&limit=10"
| jq '.[] | {time: .published, event: .eventType, outcome: .outcome.result}'

---
echo "=== Checking for post-offboard activity ===" curl -s -H "Authorization: SSWS ${OKTA_API_TOKEN}"
"${OKTA_ORG_URL}/api/v1/logs?filter=actor.alternateId+eq+"${DEPARTING_USER}"&since=$(date -u +%Y-%m-%dT%H:%M:%SZ)&limit=10"
| jq '.[] | {time: .published, event: .eventType, outcome: .outcome.result}'

---

Quick Reference

快速参考

TaskGoogle WorkspaceOktaAzure AD
Create user
gam create user
POST /api/v1/users
az ad user create
Suspend user
gam update user suspended on
POST /lifecycle/deactivate
az ad user update --account-enabled false
Enforce MFA
gam update org 2sv enforced
MFA enrollment policyConditional access policy
Revoke sessions
gam user signout
DELETE /users/{id}/sessions
revokeSignInSessions
Audit logins
gam report login
GET /api/v1/logs
GET /auditLogs/signIns
SCIM provisionBuilt-in for supported appsApp integration SCIM tabEnterprise app provisioning
任务Google WorkspaceOktaAzure AD
创建用户
gam create user
POST /api/v1/users
az ad user create
停用用户
gam update user suspended on
POST /lifecycle/deactivate
az ad user update --account-enabled false
强制实施MFA
gam update org 2sv enforced
MFA注册策略条件访问策略
撤销会话
gam user signout
DELETE /users/{id}/sessions
revokeSignInSessions
审计登录记录
gam report login
GET /api/v1/logs
GET /auditLogs/signIns
SCIM配置支持应用内置配置应用集成SCIM标签页企业应用配置