SCIM Provisioning, MFA/FIDO2, Risk-Based Auth & Enterprise Features
Welcome to the Advanced SSO Integration lab. This guide extends your Zero-Trust SSO Gateway (Project A) with enterprise-grade features including automated user provisioning, phishing-resistant MFA, adaptive authentication, directory synchronization, and production-ready observability.
The features in this lab directly mirror premium capabilities in Okta Workforce Identity, Microsoft Entra ID P2, Ping Identity, and ForgeRock. Organizations pay $6-$18 per user per month for these features — you'll implement them for free while building interview-ready skills.
This lab assumes you have completed the Zero-Trust SSO Gateway (Project A) and have a working Authentik + Traefik deployment. If you haven't completed Project A, do that first.
Upon successful completion of this lab, you will be able to:
| Feature | Okta / Azure AD | Authentik (This Lab) |
|---|---|---|
| SCIM Provisioning | Built-in | Native support |
| FIDO2/WebAuthn | Built-in | Full support |
| Conditional Access | Advanced policies | Flow-based policies |
| LDAP/AD Sync | AD Connect | LDAP Source |
| Cost (1000 users) | $6,000-$18,000/yr | $0 (self-hosted) |
| Phase | Focus | Time | Machine |
|---|---|---|---|
| Phase 1 | SCIM Provisioning | 2 hours | SERVER + BROWSER |
| Phase 2 | MFA/FIDO2 Configuration | 1.5 hours | BROWSER |
| Phase 3 | Risk-Based Authentication | 2 hours | BROWSER |
| Phase 4 | LDAP/AD Synchronization | 1.5 hours | SERVER + BROWSER |
| Phase 5 | Prometheus + Grafana Monitoring | 1.5 hours | SERVER |
| Phase 6 | High Availability Setup | 2 hours | SERVER |
Total: 10.5 hours (builds on Project A foundation)
Automate user lifecycle management across applications — no more manual account creation!
SCIM (System for Cross-domain Identity Management) is a standard protocol that allows identity providers to automatically provision, update, and deprovision user accounts in connected applications. When you add a user in Authentik, they automatically appear in GitHub, Slack, AWS, and any other SCIM-enabled app.
Without SCIM, administrators manually create accounts in every application. For a company with 500 employees and 20 apps, that's 10,000 manual account operations. SCIM automates this entirely, reducing onboarding time from hours to minutes and eliminating orphaned accounts that create security risks.
Before connecting to production applications, we'll deploy a mock SCIM server to test our configuration safely.
This Python Flask application implements SCIM 2.0 endpoints and stores users in SQLite for testing.
# Navigate to project directory
cd ~/identity-stack
# Create directory for SCIM mock server
mkdir -p scim-mock
cd scim-mock
# Create Python virtual environment
python3 -m venv venv
source venv/bin/activate
# Install required packages
pip install flask gunicorn
#!/usr/bin/env python3
"""
============================================
MOCK SCIM 2.0 SERVER
Test Authentik SCIM provisioning locally
============================================
"""
from flask import Flask, request, jsonify
import sqlite3
import uuid
from datetime import datetime
from functools import wraps
app = Flask(__name__)
# ============================================
# CONFIGURATION
# ============================================
SCIM_TOKEN = "your-secure-token-here" # Change this!
# ============================================
# DATABASE INITIALIZATION
# ============================================
def init_db():
"""Create SQLite database for storing provisioned users"""
conn = sqlite3.connect('scim_users.db')
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
userName TEXT UNIQUE NOT NULL,
givenName TEXT,
familyName TEXT,
email TEXT,
active INTEGER DEFAULT 1,
created_at TEXT,
updated_at TEXT
)
''')
conn.commit()
conn.close()
print("Database initialized")
# ============================================
# AUTHENTICATION DECORATOR
# ============================================
def require_auth(f):
"""Verify Bearer token for SCIM requests"""
@wraps(f)
def decorated(*args, **kwargs):
auth_header = request.headers.get('Authorization', '')
if not auth_header.startswith('Bearer '):
return jsonify({'error': 'Missing Authorization header'}), 401
token = auth_header[7:]
if token != SCIM_TOKEN:
return jsonify({'error': 'Invalid token'}), 401
return f(*args, **kwargs)
return decorated
# ============================================
# SCIM 2.0 ENDPOINTS
# ============================================
@app.route('/scim/v2/Users', methods=['POST'])
@require_auth
def create_user():
"""
SCIM Create User
Called when a user is added to a provisioned group in Authentik
"""
data = request.get_json()
user_id = str(uuid.uuid4())
now = datetime.utcnow().isoformat()
conn = sqlite3.connect('scim_users.db')
cursor = conn.cursor()
try:
cursor.execute('''
INSERT INTO users (id, userName, givenName, familyName, email, active, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
''', (
user_id,
data.get('userName'),
data.get('name', {}).get('givenName', ''),
data.get('name', {}).get('familyName', ''),
data.get('emails', [{}])[0].get('value', ''),
1 if data.get('active', True) else 0,
now,
now
))
conn.commit()
print(f" User created: {data.get('userName')}")
return jsonify({
'schemas': ['urn:ietf:params:scim:schemas:core:2.0:User'],
'id': user_id,
'userName': data.get('userName'),
'meta': {
'resourceType': 'User',
'created': now,
'lastModified': now
}
}), 201
except sqlite3.IntegrityError:
return jsonify({'error': 'User already exists'}), 409
finally:
conn.close()
@app.route('/scim/v2/Users/<user_id>', methods=['PATCH'])
@require_auth
def update_user(user_id):
"""
SCIM Update User
Called when user attributes change in Authentik
"""
data = request.get_json()
now = datetime.utcnow().isoformat()
conn = sqlite3.connect('scim_users.db')
cursor = conn.cursor()
# Process SCIM patch operations
for op in data.get('Operations', []):
if op.get('path') == 'active':
active = 1 if op.get('value') else 0
cursor.execute(
'UPDATE users SET active = ?, updated_at = ? WHERE id = ?',
(active, now, user_id)
)
status = "activated" if active else "deactivated"
print(f" User {user_id} {status}")
conn.commit()
conn.close()
return jsonify({'status': 'updated'}), 200
@app.route('/scim/v2/Users/<user_id>', methods=['DELETE'])
@require_auth
def delete_user(user_id):
"""
SCIM Delete User
Called when user is removed from provisioned group
"""
conn = sqlite3.connect('scim_users.db')
cursor = conn.cursor()
cursor.execute('DELETE FROM users WHERE id = ?', (user_id,))
conn.commit()
conn.close()
print(f" User deleted: {user_id}")
return '', 204
@app.route('/scim/v2/Users', methods=['GET'])
@require_auth
def list_users():
"""List all provisioned users"""
conn = sqlite3.connect('scim_users.db')
cursor = conn.cursor()
cursor.execute('SELECT * FROM users')
users = cursor.fetchall()
conn.close()
return jsonify({
'schemas': ['urn:ietf:params:scim:api:messages:2.0:ListResponse'],
'totalResults': len(users),
'Resources': [{
'id': u[0],
'userName': u[1],
'active': bool(u[5])
} for u in users]
})
if __name__ == '__main__':
init_db()
app.run(host='0.0.0.0', port=5000, debug=True)
# Ensure virtual environment is active
cd ~/identity-stack/scim-mock
source venv/bin/activate
# Start the SCIM server in the background
# Use screen or tmux to keep it running
python scim_server.py &
# Or use gunicorn for production-like setup
gunicorn -w 2 -b 0.0.0.0:5000 scim_server:app &
Test the SCIM server is running:
# Test the SCIM endpoint
curl -X GET http://localhost:5000/scim/v2/Users \
-H "Authorization: Bearer your-secure-token-here" \
-H "Content-Type: application/json"
Navigate to Applications Providers Create in Authentik:
| Setting | Value | Explanation |
|---|---|---|
| Name | Mock SCIM Provider |
Descriptive name for this provider |
| Protocol | SCIM |
Select SCIM provider type |
| URL | http://YOUR_SERVER_IP:5000/scim/v2 |
SCIM endpoint base URL |
| Token | your-secure-token-here |
Must match SCIM_TOKEN in server |
| Filter Group | scim-provisioned |
Only users in this group are provisioned |
Navigate to Directory Groups Create:
scim-provisionedscim-provisioned groupAfter adding a user to the provisioning group, check the SCIM server:
# List all provisioned users
curl -X GET http://localhost:5000/scim/v2/Users \
-H "Authorization: Bearer your-secure-token-here"
You've successfully implemented SCIM 2.0 provisioning! Users added to the provisioning group are automatically created in connected applications. This same pattern works with GitHub Enterprise, Slack, AWS SSO, and 100+ other SCIM-enabled apps.
Implement phishing-resistant authentication with hardware security keys and TOTP.
Multi-Factor Authentication (MFA) requires users to provide two or more verification factors to gain access. FIDO2/WebAuthn represents the gold standard — using hardware security keys (YubiKey, Titan) or platform authenticators (Touch ID, Windows Hello) for phishing-resistant authentication.
Hardware keys (YubiKey), biometrics
Highest SecurityGoogle/Microsoft Authenticator
High SecurityOne-time codes via email/SMS
Medium SecurityFIDO2/WebAuthn is phishing-resistant because authentication is cryptographically bound to the origin (website domain). Even if a user clicks a phishing link, the security key won't authenticate to a fake domain. Google reported eliminating account takeovers after requiring hardware keys for employees.
Navigate to Flows & Stages Stages Create:
| Setting | Value | Explanation |
|---|---|---|
| Stage Type | Authenticator WebAuthn Setup | For enrolling security keys |
| Name | webauthn-setup |
Stage identifier |
| User Verification | preferred |
Prefer biometric/PIN verification |
| Authenticator Attachment | any |
Allow hardware keys + platform auth |
Navigate to Flows & Stages Stages Create:
| Setting | Value | Explanation |
|---|---|---|
| Stage Type | Authenticator TOTP Setup | For authenticator apps |
| Name | totp-setup |
Stage identifier |
| Digits | 6 |
Standard 6-digit codes |
| Period | 30 |
Seconds per code rotation |
Navigate to Flows & Stages Stages Create:
| Setting | Value | Explanation |
|---|---|---|
| Stage Type | Authenticator Validation | Validates MFA during login |
| Name | mfa-validation |
Stage identifier |
| Device Classes | WebAuthn, TOTP | Accepted MFA methods |
| Not Configured Action | Force user to configure |
Require MFA enrollment |
Navigate to Flows & Stages Flows default-authentication-flow Stage Bindings:
mfa-validation20 (after password stage at order 10)Test the MFA flow:
https://authentik.yourdomain.comYou've implemented enterprise-grade MFA with FIDO2/WebAuthn and TOTP support. All users must now authenticate with two factors — something they know (password) and something they have (security key or phone).
Common issues and their solutions for advanced SSO features.
docker logs authentik-server | grep SCIMldapsearch -x -H ldap://server -D "binddn" -w pass -b "basedn"curl http://authentik-server:9300/metricshttp://localhost:9090/targetsRemove lab components when finished to free resources.
# Stop SCIM mock server
pkill -f scim_server
# Stop monitoring stack (if deployed)
cd ~/identity-stack
docker compose -f docker-compose.monitoring.yml down
# Main stack continues running
This will remove all configurations from this lab. Your Project A base installation remains intact.
# Remove SCIM mock server
rm -rf ~/identity-stack/scim-mock
# Remove monitoring stack and data
docker compose -f docker-compose.monitoring.yml down -v
rm -rf ~/identity-stack/prometheus.yml
# Remove HA configuration files (if created)
rm -rf ~/identity-stack/ha-config
You've completed the Advanced SSO Integration lab and acquired enterprise-level IAM skills.
| Interview Question | Your Answer |
|---|---|
| "How do you automate user provisioning?" | SCIM 2.0 protocol. The IdP pushes user lifecycle events (create/update/delete) to applications via REST API. I implemented this with Authentik SCIM providers. |
| "What's the most secure MFA method?" | FIDO2/WebAuthn with hardware security keys. It's phishing-resistant because authentication is cryptographically bound to the origin — the key won't work on fake domains. |
| "How do you implement conditional access?" | Risk-based policies that evaluate context: device trust, location, time, and application sensitivity. Higher risk triggers stronger authentication requirements. |
| "How do you ensure SSO availability?" | Multi-node deployment with shared PostgreSQL and Redis. Load balancer distributes traffic across nodes with automatic failover if one node fails. |