MFA SCIM

Advanced SSO Integration

SCIM Provisioning, MFA, Risk-Based Auth & Enterprise Features

Authentik SCIM 2.0 MFA/FIDO2 Risk-Based Monitoring HA Ready
0

Overview

Enterprise-Grade SSO Extensions

🛡️
Extending Project A to Enterprise Scale

Add automated provisioning, strong MFA, adaptive authentication, and observability

🔄
SCIM Provisioning
Automated user lifecycle to apps
🔐
MFA/FIDO2
Hardware keys, TOTP, WebAuthn
🎯
Risk-Based Auth
Conditional access policies
📁
LDAP Sync
AD/OpenLDAP integration
📊
Monitoring
Prometheus & Grafana
HA Setup
Multi-node deployment

Enterprise SSO Architecture

👤 Users
MFA Required
🔐 Authentik
SSO + Risk Engine
📱 Applications
OIDC/SAML
📁 LDAP/AD
User Sync
🔐
🔄 SCIM
Provisioning
📊 Prometheus
Metrics
Telemetry

🏢 Enterprise Feature Comparison

Feature Okta/Azure AD Authentik (This Guide)
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)
⏱️

Time Investment

Module Focus Time
SCIM Provisioning Automated user lifecycle 2 hours
MFA/FIDO2 Hardware keys, TOTP, WebAuthn 1.5 hours
Risk-Based Auth Conditional access flows 2 hours
LDAP Sync Directory integration 1.5 hours
Monitoring Prometheus + Grafana 1.5 hours
High Availability Multi-node deployment 2 hours

Total: 10.5 hours (builds on Project A)

1

SCIM Provisioning

Automated User Lifecycle Management

🔄
No More Manual User Creation

Users created in Authentik automatically appear in connected applications

1
Create SCIM Provider in Authentik
⏱️ 15 min

Navigate to Applications → Providers → Create

⚙️ SCIM Provider Configuration
# ============================================
# SCIM PROVIDER SETTINGS
# ============================================

Name: "GitHub Enterprise SCIM"
Protocol: "SCIM"

# Target application URL (where users are provisioned)
URL: "https://api.github.com/scim/v2/organizations/YOUR_ORG"

# Bearer token for authentication
Token: "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# Which users to provision
Filter Group: "github-users"

# Attribute Mapping
User Mapping:
  - userName → user.username
  - name.givenName → user.name
  - name.familyName → user.attributes.last_name
  - emails[0].value → user.email
  - active → user.is_active

# ============================================
# COMMON SCIM ENDPOINTS
# ============================================

# GitHub Enterprise
https://api.github.com/scim/v2/organizations/{org}

# Slack Enterprise Grid
https://api.slack.com/scim/v2

# AWS SSO
https://scim.{region}.amazonaws.com/{tenant-id}/scim/v2

# Zoom
https://api.zoom.us/scim2
2
Test with Mock SCIM Server
⏱️ 30 min
🐍 scim_mock_server.py - Test SCIM Endpoint
#!/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

app = Flask(__name__)

# Initialize database
def init_db():
    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()


# ============================================
# SCIM 2.0 ENDPOINTS
# ============================================

@app.route('/scim/v2/Users', methods=['POST'])
def create_user():
    """SCIM Create User - Called when user added to provisioned group"""
    data = request.get_json()
    user_id = str(uuid.uuid4())
    
    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,
            datetime.utcnow().isoformat(),
            datetime.utcnow().isoformat()
        ))
        conn.commit()
        
        print(f"✅ SCIM CREATE: {data.get('userName')}")
        
        return jsonify({
            "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
            "id": user_id,
            "userName": data.get('userName'),
            "active": data.get('active', True),
            "meta": {
                "resourceType": "User",
                "created": datetime.utcnow().isoformat(),
                "location": f"/scim/v2/Users/{user_id}"
            }
        }), 201
    except sqlite3.IntegrityError:
        return jsonify({"error": "User already exists"}), 409
    finally:
        conn.close()


@app.route('/scim/v2/Users/<user_id>', methods=['PATCH'])
def update_user(user_id):
    """SCIM Update User - Called on attribute changes or deprovisioning"""
    data = request.get_json()
    
    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, datetime.utcnow().isoformat(), user_id))
            
            status = "ENABLED" if active else "DISABLED"
            print(f"⚠️ SCIM UPDATE: User {user_id} - {status}")
    
    conn.commit()
    conn.close()
    
    return jsonify({"id": user_id, "status": "updated"}), 200


@app.route('/scim/v2/Users/<user_id>', methods=['DELETE'])
def delete_user(user_id):
    """SCIM Delete User - Full deprovisioning"""
    conn = sqlite3.connect('scim_users.db')
    cursor = conn.cursor()
    cursor.execute('DELETE FROM users WHERE id = ?', (user_id,))
    conn.commit()
    conn.close()
    
    print(f"🗑️ SCIM DELETE: User {user_id}")
    return '', 204


@app.route('/scim/v2/Users', methods=['GET'])
def list_users():
    """SCIM List Users - For reconciliation"""
    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()
    print("🚀 SCIM Mock Server: http://localhost:5005/scim/v2")
    app.run(host='0.0.0.0', port=5005, debug=True)
🔄
Enterprise Parallel

This is exactly how Okta, Azure AD, and CyberArk Identity provision users to SaaS apps. SCIM is the industry standard.

2

MFA & FIDO2 Configuration

Phishing-Resistant Authentication

🔐
Hardware Keys = Phishing Immunity

FIDO2/WebAuthn provides the strongest MFA - no codes to intercept

🔑
FIDO2/WebAuthn
YubiKey, Touch ID, Windows Hello
Highest Security
📱
TOTP
Google Authenticator, Authy
Medium Security
💬
SMS/Email
One-time codes via text/email
Lower Security
🔔
Push Notification
Mobile app approval
Medium Security
1
Enable WebAuthn/FIDO2 in Authentik
⏱️ 15 min

Navigate to Flows & Stages → Stages → Create

🔑 WebAuthn Stage Configuration
# ============================================
# WEBAUTHN AUTHENTICATOR SETUP STAGE
# For enrolling security keys
# ============================================

Stage Type: "Authenticator WebAuthn Setup"
Name: "webauthn-setup"

# Supported authenticator types
User Verification: "preferred"  # or "required" for higher security
Resident Key Requirement: "preferred"

# Device types to allow
Authenticator Attachment: "any"  # platform, cross-platform, or any

# ============================================
# WEBAUTHN VALIDATION STAGE
# For validating during login
# ============================================

Stage Type: "Authenticator Validation"
Name: "mfa-validation"

Device Classes:
  ✅ WebAuthn Authenticators
  ✅ TOTP Authenticators  
  ☐ Static Tokens (disable for security)

Not Configured Action: "Force user to configure"

# ============================================
# ADD TO AUTHENTICATION FLOW
# ============================================

# Go to Flows → default-authentication-flow → Stage Bindings
# Add the mfa-validation stage AFTER password verification

Order: 20  # After password stage (order 10)
Stage: mfa-validation
Evaluate on plan: ✅
Re-evaluate policies: ☐
2
Configure TOTP (Authenticator Apps)
⏱️ 10 min
📱 TOTP Stage Configuration
# ============================================
# TOTP AUTHENTICATOR SETUP STAGE
# ============================================

Stage Type: "Authenticator TOTP Setup"
Name: "totp-setup"

# TOTP Configuration
Digits: 6  # Standard 6-digit codes
Algorithm: "SHA1"  # Most compatible
Period: 30  # Seconds per code

# ============================================
# ADD MFA ENROLLMENT FLOW
# ============================================

# Create new flow: mfa-enrollment-flow
Flow Name: "MFA Enrollment"
Designation: "Stage Configuration"

# Stage Bindings (in order):
1. totp-setup (Order: 10)
2. webauthn-setup (Order: 20)

# Link to user settings
# Go to Flows → default-user-settings-flow
# Add link to mfa-enrollment-flow
3
Enforce MFA with Policy
⏱️ 15 min
📜 MFA Enforcement Policy
# ============================================
# CREATE MFA REQUIREMENT POLICY
# Navigate to: Customization → Policies → Create
# ============================================

Policy Type: "Expression Policy"
Name: "require-mfa-for-admins"

# Expression (Python-like syntax)
Expression:
"""
# Require MFA for users in admin groups
if ak_is_group_member(request.user, name="authentik Admins"):
    # Check if user has MFA configured
    from authentik.stages.authenticator_totp.models import TOTPDevice
    from authentik.stages.authenticator_webauthn.models import WebAuthnDevice
    
    has_totp = TOTPDevice.objects.filter(user=request.user, confirmed=True).exists()
    has_webauthn = WebAuthnDevice.objects.filter(user=request.user).exists()
    
    if not (has_totp or has_webauthn):
        # Redirect to MFA enrollment
        return False
        
return True
"""

# ============================================
# ALTERNATIVE: SIMPLE GROUP-BASED POLICY
# ============================================

Policy Type: "Expression Policy"
Name: "mfa-required-all-users"

Expression:
"""
# Simply require MFA validation for all authenticated users
return request.user.is_authenticated
"""

# Bind this policy to the mfa-validation stage binding
3

Risk-Based Authentication

Conditional Access Policies

🎯
Context-Aware Security

Require more authentication for risky situations - new device, unusual location, sensitive app

⚠️

Risk Signals in Authentik

Signal Detection Response
New Device First login from browser/device Force MFA + email notification
New Location IP geolocation change Additional verification
Impossible Travel Login from distant location too quickly Block + alert
Sensitive App Access to high-value application Always require MFA
Failed Attempts Multiple password failures CAPTCHA + rate limit
1
Create Conditional Access Flow
⏱️ 30 min
🔀 Risk-Based Authentication Flow
# ============================================
# RISK-BASED AUTHENTICATION FLOW
# ============================================

# 1. Create GeoIP Policy
Policy Type: "Expression Policy"
Name: "block-high-risk-countries"

Expression:
"""
# Block logins from high-risk countries
BLOCKED_COUNTRIES = ["RU", "CN", "KP", "IR"]

# Get GeoIP data from request context
geo_country = request.context.get("geoip", {}).get("country", "")

if geo_country in BLOCKED_COUNTRIES:
    ak_message("Access denied from your location")
    return False
    
return True
"""

# ============================================
# 2. Create New Device Detection Policy
# ============================================

Policy Type: "Expression Policy"
Name: "detect-new-device"

Expression:
"""
# Check if this is a new device/browser
from authentik.core.models import AuthenticatedSession

user = request.user
client_ip = request.context.get("client_ip", "")

# Check for existing sessions from this IP
existing_sessions = AuthenticatedSession.objects.filter(
    user=user,
    last_ip=client_ip
).exists()

# If new device, set flag for MFA requirement
if not existing_sessions:
    request.context["new_device"] = True
    
return True
"""

# ============================================
# 3. Create Sensitive App Policy
# ============================================

Policy Type: "Expression Policy"
Name: "sensitive-app-mfa"

Expression:
"""
# Always require MFA for sensitive applications
SENSITIVE_APPS = ["vault", "aws-console", "production-db"]

app_slug = request.context.get("application", {}).get("slug", "")

if app_slug in SENSITIVE_APPS:
    request.context["require_mfa"] = True
    
return True
"""

# ============================================
# 4. Bind Policies to Flow Stages
# ============================================

# Add policies to stage bindings:
# - block-high-risk-countries → identification stage
# - detect-new-device → password stage
# - sensitive-app-mfa → mfa-validation stage
4

LDAP/AD Synchronization

Enterprise Directory Integration

📁
Single Source of Truth

Sync users and groups from Active Directory or OpenLDAP

1
Create LDAP Source
⏱️ 20 min

Navigate to Directory → Federation & Social Login → Create → LDAP Source

📁 LDAP Source Configuration
# ============================================
# LDAP SOURCE CONFIGURATION
# ============================================

Name: "Active Directory"
Slug: "active-directory"

# Connection Settings
Server URI: "ldaps://dc01.corp.local:636"  # Use LDAPS!
Enable StartTLS: ☐  # Not needed with LDAPS
TLS Verification Certificate: "(your CA cert)"

# Bind Credentials
Bind CN: "CN=authentik-svc,OU=Service Accounts,DC=corp,DC=local"
Bind Password: "*********"

# Search Settings
Base DN: "DC=corp,DC=local"

# User Settings
User Object Filter: "(objectClass=user)"
User Group Membership Field: "memberOf"

# Group Settings
Group Object Filter: "(objectClass=group)"
Group Membership Field: "member"

# ============================================
# PROPERTY MAPPINGS
# ============================================

# Map AD attributes to Authentik user fields
User Property Mappings:
  ✅ authentik default LDAP Mapping: mail
  ✅ authentik default LDAP Mapping: Name
  ✅ authentik default LDAP Mapping: sAMAccountName (username)
  
# Map AD groups to Authentik groups
Group Property Mappings:
  ✅ authentik default LDAP Mapping: cn

# ============================================
# SYNC SETTINGS
# ============================================

Sync Users: ✅
Sync User Password: ☐  # Users auth directly to AD
Sync Groups: ✅
Sync Parent Group: "AD Users"  # Optional parent group
2
Run LDAP Sync
⏱️ 5 min
🔄 Manual & Scheduled Sync
# ============================================
# TRIGGER MANUAL SYNC
# ============================================

# In Authentik UI:
# 1. Go to Directory → Federation & Social Login
# 2. Click on your LDAP Source
# 3. Click "Run sync now" button

# Via API:
curl -X POST "https://authentik.yourdomain.com/api/v3/sources/ldap/{source-slug}/sync/" \
  -H "Authorization: Bearer YOUR_API_TOKEN"

# ============================================
# SCHEDULED SYNC (via System Tasks)
# ============================================

# Authentik automatically schedules LDAP sync
# Check: System → System Tasks
# Look for: ldap_sync_{source-name}

# Default interval: Every 60 minutes
# Can be customized via environment variable:
AUTHENTIK_LDAP_SYNC_INTERVAL="30"  # 30 minutes

# ============================================
# VERIFY SYNC RESULTS
# ============================================

# Check synced users:
# Directory → Users → Filter by "Source: Active Directory"

# Check synced groups:
# Directory → Groups → Look for AD groups
5

Prometheus Monitoring

Observability & Alerting

📊
Know Everything

Login attempts, failed authentications, session count, API usage

1
Add Prometheus & Grafana
⏱️ 20 min
🐳 docker-compose.monitoring.yml
# ============================================
# ADD TO YOUR docker-compose.yml
# ============================================

  prometheus:
    image: prom/prometheus:v2.47.0
    container_name: prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus-data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
    networks:
      - authentik

  grafana:
    image: grafana/grafana:10.1.0
    container_name: grafana
    ports:
      - "3000:3000"
    environment:
      GF_SECURITY_ADMIN_PASSWORD: "admin123"
      GF_AUTH_GENERIC_OAUTH_ENABLED: "true"
      GF_AUTH_GENERIC_OAUTH_NAME: "Authentik"
      GF_AUTH_GENERIC_OAUTH_CLIENT_ID: "grafana"
      GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET: "your-secret"
      GF_AUTH_GENERIC_OAUTH_AUTH_URL: "https://authentik.yourdomain.com/application/o/authorize/"
      GF_AUTH_GENERIC_OAUTH_TOKEN_URL: "https://authentik.yourdomain.com/application/o/token/"
      GF_AUTH_GENERIC_OAUTH_API_URL: "https://authentik.yourdomain.com/application/o/userinfo/"
    volumes:
      - grafana-data:/var/lib/grafana
    depends_on:
      - prometheus
    networks:
      - authentik

volumes:
  prometheus-data:
  grafana-data:
📝 prometheus.yml
# ============================================
# PROMETHEUS CONFIGURATION
# prometheus.yml
# ============================================

global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  # Authentik metrics
  - job_name: 'authentik'
    static_configs:
      - targets: ['authentik-server:9300']
    metrics_path: '/metrics'

  # Authentik worker metrics
  - job_name: 'authentik-worker'
    static_configs:
      - targets: ['authentik-worker:9300']

  # Prometheus itself
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']
📈

Key Authentik Metrics

Metric Description Alert Threshold
authentik_login_total Total login attempts Spike detection
authentik_login_failed_total Failed login attempts > 10/min = alert
authentik_sessions_total Active sessions Capacity planning
authentik_flows_execution_time Flow execution latency > 5s = investigate
6

High Availability Setup

Multi-Node Deployment

Zero Downtime SSO

Multiple Authentik servers behind a load balancer for resilience

1
HA Architecture Overview
⏱️ 10 min
🌐 Load Balancer
Traefik/HAProxy
🔐 Server 1
🔐 Server 2
🔐 Server 3
🐘 PostgreSQL
Shared Database
🔴 Redis
Session Store
2
Multi-Server Docker Compose
⏱️ 30 min
🐳 docker-compose.ha.yml
# ============================================
# HIGH AVAILABILITY AUTHENTIK
# docker-compose.ha.yml
# ============================================

version: "3.8"

services:
  # ----------------------------------------
  # PostgreSQL - Shared Database
  # ----------------------------------------
  postgresql:
    image: postgres:15
    container_name: authentik-db
    environment:
      POSTGRES_DB: "authentik"
      POSTGRES_USER: "authentik"
      POSTGRES_PASSWORD: "${PG_PASS}"
    volumes:
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U authentik"]
      interval: 5s
      timeout: 5s
      retries: 5
    networks:
      - authentik

  # ----------------------------------------
  # Redis - Session & Cache Store
  # ----------------------------------------
  redis:
    image: redis:7-alpine
    container_name: authentik-redis
    command: redis-server --requirepass ${REDIS_PASS}
    volumes:
      - redis-data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
    networks:
      - authentik

  # ----------------------------------------
  # Authentik Server - Node 1
  # ----------------------------------------
  server-1:
    image: ghcr.io/goauthentik/server:2024.2.2
    container_name: authentik-server-1
    command: server
    environment:
      AUTHENTIK_REDIS__HOST: redis
      AUTHENTIK_REDIS__PASSWORD: "${REDIS_PASS}"
      AUTHENTIK_POSTGRESQL__HOST: postgresql
      AUTHENTIK_POSTGRESQL__USER: "authentik"
      AUTHENTIK_POSTGRESQL__PASSWORD: "${PG_PASS}"
      AUTHENTIK_POSTGRESQL__NAME: "authentik"
      AUTHENTIK_SECRET_KEY: "${AUTHENTIK_SECRET_KEY}"
    labels:
      - "traefik.enable=true"
      - "traefik.http.services.authentik.loadbalancer.server.port=9000"
    depends_on:
      - postgresql
      - redis
    networks:
      - authentik

  # ----------------------------------------
  # Authentik Server - Node 2
  # ----------------------------------------
  server-2:
    image: ghcr.io/goauthentik/server:2024.2.2
    container_name: authentik-server-2
    command: server
    environment:
      # Same environment as server-1
      AUTHENTIK_REDIS__HOST: redis
      AUTHENTIK_REDIS__PASSWORD: "${REDIS_PASS}"
      AUTHENTIK_POSTGRESQL__HOST: postgresql
      AUTHENTIK_POSTGRESQL__USER: "authentik"
      AUTHENTIK_POSTGRESQL__PASSWORD: "${PG_PASS}"
      AUTHENTIK_POSTGRESQL__NAME: "authentik"
      AUTHENTIK_SECRET_KEY: "${AUTHENTIK_SECRET_KEY}"
    labels:
      - "traefik.enable=true"
      - "traefik.http.services.authentik.loadbalancer.server.port=9000"
    depends_on:
      - postgresql
      - redis
    networks:
      - authentik

  # ----------------------------------------
  # Shared Worker (only need one)
  # ----------------------------------------
  worker:
    image: ghcr.io/goauthentik/server:2024.2.2
    container_name: authentik-worker
    command: worker
    environment:
      AUTHENTIK_REDIS__HOST: redis
      AUTHENTIK_REDIS__PASSWORD: "${REDIS_PASS}"
      AUTHENTIK_POSTGRESQL__HOST: postgresql
      AUTHENTIK_POSTGRESQL__USER: "authentik"
      AUTHENTIK_POSTGRESQL__PASSWORD: "${PG_PASS}"
      AUTHENTIK_POSTGRESQL__NAME: "authentik"
      AUTHENTIK_SECRET_KEY: "${AUTHENTIK_SECRET_KEY}"
    depends_on:
      - postgresql
      - redis
    networks:
      - authentik

volumes:
  postgres-data:
  redis-data:

networks:
  authentik:
💡
Key HA Requirements
  • Same AUTHENTIK_SECRET_KEY on all nodes
  • Shared PostgreSQL database
  • Shared Redis for sessions/cache
  • Load balancer with sticky sessions (optional but recommended)
7

Testing & Skills Summary

Validate your enterprise SSO

Validation Checklist

  • SCIM provider creates users in mock server
  • SCIM updates sync attribute changes
  • WebAuthn/FIDO2 enrollment works
  • TOTP authenticator setup works
  • MFA is required during login
  • Risk policies block high-risk countries
  • LDAP sync imports users/groups
  • Prometheus scrapes Authentik metrics
  • Grafana shows login dashboard
  • HA failover works between nodes
🎓

Skills Acquired

  • SCIM Provisioning: Automated user lifecycle
  • MFA/FIDO2: Phishing-resistant authentication
  • Risk-Based Auth: Conditional access policies
  • LDAP Integration: Enterprise directory sync
  • Observability: Prometheus metrics + Grafana
  • High Availability: Multi-node deployment
  • Policy Design: Expression-based policies

🏢 Enterprise IAM Interview Ready

Interview Question Your Answer
"How do you automate user provisioning?" SCIM 2.0 protocol. IdP pushes user lifecycle events (create/update/delete) to applications. Implemented with Authentik SCIM providers.
"What's the most secure MFA?" FIDO2/WebAuthn with hardware security keys. Phishing-resistant because authentication is bound to the origin. No codes to intercept.
"How do you implement conditional access?" Risk-based policies evaluating context: device, location, time, app sensitivity. Higher risk = stronger authentication requirements.
"How do you ensure SSO availability?" Multi-node deployment with shared PostgreSQL and Redis. Load balancer distributes traffic. Automatic failover if node fails.
🚀

Continue Your Journey

  • Project B: HashiCorp Vault JIT Access
  • Project B+: Advanced Vault (LDAP, SSH, AWS)
  • Project E: Identity Governance (Access Reviews)
  • Add Passkey support (passwordless)
  • Implement RADIUS for VPN integration