SOC 2 Type II is the attestation that enterprise buyers increasingly require before signing contracts for SaaS products that handle their data. The audit covers five trust service criteria; for most SaaS companies, the Security criterion is the core focus. Within Security, the CC6 (Logical and Physical Access Controls) category directly covers authentication and access management. Here is what auditors will examine and the evidence you need to produce.
CC6.1: Logical access security software
CC6.1 asks whether the organization has implemented logical access security measures to protect against threats from sources outside the system boundaries. For authentication, auditors look for:
- An access control policy that defines who can access what systems
- User authentication requirements documented and enforced
- Firewall and network access controls for your infrastructure
- Evidence that these controls are configured and functioning
The evidence package typically includes: screenshots of your authentication configuration showing required MFA, access control policy documents, firewall rule exports, and a list of systems with their access controls.
CC6.2: Prior to issuing system credentials
This control requires that the organization registers and authorizes new internal and external users whose access is administered by the organization. Key requirements:
- A formal process for provisioning user access — not informal Slack messages
- Access requests approved by the appropriate manager or system owner
- New accounts created with only the minimum required permissions
- Evidence of the approval process (tickets, email chains, JIRA issues)
// Example access provisioning audit trail
{
"request_id": "ACC-2022-0047",
"requester": "manager@example.com",
"approver": "security@example.com",
"approved_at": "2022-01-10T14:23:00Z",
"user": "newengineer@example.com",
"access_granted": ["github:read", "aws:dev-environment", "datadog:viewer"],
"access_denied": ["aws:production", "database:admin"],
"reason_for_denial": "Principle of least privilege — dev role sufficient",
"account_created_at": "2022-01-10T15:00:00Z"
}
CC6.3: Role-based access and least privilege
CC6.3 requires that access is assigned based on job responsibilities, and that the principle of least privilege is applied. Auditors will look for documented role definitions, evidence that users only have the permissions required for their role, and a process for reviewing and removing unnecessary access.
For your application's admin console and infrastructure access, this means: each person has exactly the role they need, not admin access by default. Production database access should be exceptional and audit-logged. Cloud IAM roles should be defined with specific allowed actions, not blanket * policies.
CC6.6: Authentication of external users
CC6.6 specifically requires that remote users authenticate via strong authentication methods. For SaaS products, this means:
- MFA required for all internal employee access to production systems
- MFA available for end users (and potentially required for admin roles within your product)
- Password policy enforcing minimum length and breach checking
- Session timeouts enforced
// Evidence: MFA enforcement policy (documented in code)
// This can be presented to auditors as control evidence
// Enforce MFA for all privileged operations
const PRIVILEGED_OPERATIONS = [
'user.delete', 'billing.modify', 'api_key.create',
'sso.configure', 'admin.impersonate', 'data.export'
];
async function requireMfaForPrivileged(
req: AuthenticatedRequest,
operation: string
): Promise<void> {
if (!PRIVILEGED_OPERATIONS.includes(operation)) return;
const session = req.session;
const mfaAge = (Date.now() - session.last_mfa_at) / 1000;
if (mfaAge > 300) { // Require fresh MFA within 5 minutes
throw new RequiresMfaError('Please re-verify with MFA to continue');
}
}
// Session timeout enforcement
const SESSION_TIMEOUT_SECONDS = 8 * 3600; // 8 hours
app.use(async (req, res, next) => {
if (!req.session) return next();
const sessionAge = (Date.now() / 1000) - req.session.created_at;
if (sessionAge > SESSION_TIMEOUT_SECONDS) {
await destroySession(req.session.id);
return res.status(401).json({ error: 'session_expired' });
}
next();
});
CC6.7: Transmission and movement of data
CC6.7 requires that the transmission of information uses encryption. For auth endpoints, this means: all authentication endpoints must be served over HTTPS only, HSTS is configured, and certificates are valid and monitored for expiry. Auditors will test your endpoints and check certificate configurations.
CC6.8: Controls prevent or detect unauthorized changes
CC6.8 covers change management — ensuring that changes to the system are authorized and reviewed. For auth infrastructure, this means infrastructure-as-code is in source control, all changes go through pull request review, privileged access changes require manager approval, and production deployments are logged.
Access review evidence
A common audit finding is the absence of regular access reviews. SOC 2 requires that you periodically review who has access to what and remove access that is no longer needed. Quarterly reviews are the common standard. The evidence is a spreadsheet or ticket showing the review was performed, who reviewed it, and any access that was removed as a result.
// Automated access review reminder and reporting
async function generateAccessReviewReport(quarter: string): Promise<AccessReview> {
const users = await db.users.findAll({
role: { $in: ['admin', 'operator'] },
last_review_at: { $lt: new Date(Date.now() - 90 * 86400 * 1000) }
});
const report = {
quarter,
generated_at: new Date(),
total_privileged_users: users.length,
users_needing_review: users.map(u => ({
user_id: u.id,
email: u.email,
role: u.role,
last_active: u.last_active_at,
granted_by: u.granted_by,
granted_at: u.role_granted_at
})),
dormant_accounts: users.filter(u => {
const daysSinceActive = (Date.now() - u.last_active_at.getTime()) / 86400000;
return daysSinceActive > 90;
})
};
// Export to your audit evidence repository
await auditEvidenceStore.store(`access-review-${quarter}.json`, report);
return report;
}