23 KiB
23 KiB
IAM Service Architecture
Architecture documentation for IAM (Identity and Access Management) Service built with .NET 10, Duende IdentityServer, and Clean Architecture.
Architecture Overview
graph TB
subgraph "Clients"
WEB[Web App]
MOB[Mobile App]
SVC[Other Services]
SOCIAL[Google/Facebook]
end
subgraph "API Layer"
AUTH[AuthController]
USR[UsersController]
TOK[Token Endpoint]
end
subgraph "Application Layer - CQRS"
CMD[Commands]
QRY[Queries]
VAL[Validators]
BHV[Behaviors]
end
subgraph "Domain Layer"
USER[User Aggregate]
ROLE[Role Aggregate]
EVT[Domain Events]
end
subgraph "Infrastructure"
CTX[Identity DbContext]
REPO[Repositories]
IDSERVER[Duende IdentityServer]
EMAIL[Email Service]
TOTP[2FA Service]
OAUTH[Social Login Service]
end
subgraph "External"
DB[(PostgreSQL)]
REDIS[(Redis)]
SMTP[SMTP Server]
end
WEB --> AUTH
MOB --> AUTH
SVC --> TOK
SOCIAL --> AUTH
AUTH --> CMD
AUTH --> QRY
USR --> CMD
USR --> QRY
TOK --> IDSERVER
CMD --> VAL
CMD --> BHV
CMD --> USER
QRY --> REPO
USER --> EVT
REPO --> CTX
IDSERVER --> CTX
CTX --> DB
CTX --> REDIS
EMAIL --> SMTP
OAUTH --> SOCIAL
style AUTH fill:#4a90d9,stroke:#2d5986,color:#fff
style USER fill:#50c878,stroke:#2d8659,color:#fff
style DB fill:#ff6b6b,stroke:#c0392b,color:#fff
style IDSERVER fill:#9b59b6,stroke:#7d3c98,color:#fff
style EMAIL fill:#e67e22,stroke:#d35400,color:#fff
style TOTP fill:#1abc9c,stroke:#16a085,color:#fff
style OAUTH fill:#3498db,stroke:#2980b9,color:#fff
OAuth2 Authentication Flow
sequenceDiagram
participant Client
participant AuthController
participant IdentityServer
participant UserManager
participant Database
Note over Client,Database: Password Grant Flow (User Login)
Client->>AuthController: POST /connect/token<br/>grant_type=password
AuthController->>IdentityServer: Validate Request
IdentityServer->>UserManager: FindByEmailAsync()
UserManager->>Database: Query User
Database-->>UserManager: User Data
UserManager->>UserManager: CheckPasswordAsync()
UserManager-->>IdentityServer: User Validated
IdentityServer->>IdentityServer: Generate Tokens (JWT)
IdentityServer-->>AuthController: Token Response
AuthController-->>Client: access_token + refresh_token
Note over Client,Database: Using Access Token
Client->>AuthController: GET /api/v1/users/me<br/>Authorization: Bearer {token}
AuthController->>IdentityServer: Validate JWT
IdentityServer-->>AuthController: Claims Principal
AuthController-->>Client: User Data
Token Types and Flows
graph LR
subgraph "Grant Types"
PWD[Password Grant]
REF[Refresh Token]
CC[Client Credentials]
end
subgraph "Tokens"
AT[Access Token<br/>15 min]
RT[Refresh Token<br/>7 days]
end
subgraph "Use Cases"
USER[User Login]
RENEW[Token Renewal]
S2S[Service-to-Service]
end
PWD --> AT
PWD --> RT
REF --> AT
CC --> AT
USER --> PWD
RENEW --> REF
S2S --> CC
style AT fill:#2ecc71,stroke:#27ae60,color:#fff
style RT fill:#f39c12,stroke:#d68910,color:#fff
style CC fill:#9b59b6,stroke:#7d3c98,color:#fff
Domain Model
User Aggregate
classDiagram
class ApplicationUser {
+Guid Id
+string Email
+string FirstName
+string LastName
+UserStatus Status
+DateTime CreatedAt
+DateTime? LastLoginAt
+UpdateProfile(firstName, lastName)
+Disable()
+RecordLogin()
}
class UserStatus {
<<enumeration>>
+Active
+Locked
+Disabled
+PendingVerification
}
class ApplicationRole {
+Guid Id
+string Name
+string Description
}
ApplicationUser --> UserStatus : has
ApplicationUser "many" --> "many" ApplicationRole : belongs to
Database Schema
erDiagram
AspNetUsers {
uuid Id PK
string Email UK
string PasswordHash
string FirstName
string LastName
int StatusId FK
datetime CreatedAt
datetime LastLoginAt
}
UserStatuses {
int Id PK
string Name
}
AspNetRoles {
uuid Id PK
string Name UK
string Description
}
AspNetUserRoles {
uuid UserId PK,FK
uuid RoleId PK,FK
}
IdentityServerPersistedGrants {
uuid Id PK
string Key UK
string Type
string ClientId
datetime CreationTime
datetime Expiration
}
AspNetUsers ||--o{ UserStatuses : has
AspNetUsers ||--o{ AspNetUserRoles : has
AspNetRoles ||--o{ AspNetUserRoles : has
Phase 2: Organization & Group Aggregates
classDiagram
class Organization {
+Guid Id
+string Name
+string Slug
+Guid? ParentId
+OrganizationStatus Status
+Create()
+Update()
+Archive()
}
class Group {
+Guid Id
+Guid OrganizationId
+string Name
+string Description
+AddMember()
+RemoveMember()
}
class GroupMember {
+Guid GroupId
+Guid UserId
+GroupRole Role
+DateTime JoinedAt
}
Organization "1" --> "*" Group : contains
Group "1" --> "*" GroupMember : has
Phase 3A: Access Request Aggregate
classDiagram
class AccessRequest {
+Guid Id
+Guid RequesterId
+string ResourceType
+Guid ResourceId
+string RequestedPermission
+AccessRequestStatus Status
+AccessRequestPriority Priority
+DateTime DueDate
+Submit()
+Approve()
+Reject()
+Cancel()
}
class AccessRequestApprover {
+Guid RequestId
+Guid UserId
+int ApprovalOrder
+ApproverStatus Status
+string Comments
+Approve()
+Reject()
}
class AccessRequestStatus {
<<enumeration>>
+Draft
+Pending
+Approved
+Rejected
+Cancelled
+Expired
}
AccessRequest "1" --> "*" AccessRequestApprover : has
AccessRequest --> AccessRequestStatus : has
Phase 3B: Access Review & PAM Aggregates
classDiagram
class AccessReview {
+Guid Id
+string Name
+Guid OwnerId
+string Scope
+AccessReviewStatus Status
+DateTime DueDate
+Start()
+Complete()
+Cancel()
}
class AccessReviewItem {
+Guid Id
+Guid UserId
+string ResourceType
+Guid ResourceId
+ReviewDecision Decision
+Certify()
+Revoke()
}
class PrivilegedAccessGrant {
+Guid Id
+Guid UserId
+Guid RoleId
+string ResourceScope
+PrivilegedAccessStatus Status
+DateTime StartsAt
+DateTime ExpiresAt
+Activate()
+Revoke()
+Extend()
}
AccessReview "1" --> "*" AccessReviewItem : contains
Phase 4A: Audit & Compliance Aggregates
classDiagram
class AuditLog {
+Guid Id
+AuditEventType EventType
+Guid? ActorId
+string ResourceType
+Guid? ResourceId
+string Action
+bool Success
+DateTime Timestamp
+LoginEvent()
+AccessGrantedEvent()
}
class ComplianceReport {
+Guid Id
+string Name
+ComplianceReportType ReportType
+ComplianceReportStatus Status
+int TotalChecks
+int PassedChecks
+double CompliancePercentage
+StartGenerating()
+Complete()
+Fail()
}
class ComplianceViolation {
+Guid Id
+string Rule
+ViolationSeverity Severity
+string Description
+bool Resolved
+Resolve()
}
ComplianceReport "1" --> "*" ComplianceViolation : has
AuditEventType (18 Event Types)
| Category | Event Types |
|---|---|
| Authentication | Login, Logout, LoginFailed, PasswordChanged, TwoFactorEnabled/Disabled |
| User Management | UserCreated, UserUpdated, UserDeleted, UserLocked/Unlocked |
| Access Control | AccessRequested, AccessGranted, AccessRevoked, AccessDenied, PrivilegedAccessGranted/Revoked |
| Organization | OrganizationCreated/Updated, GroupMemberAdded/Removed |
| Policy | PolicyCreated, PolicyActivated, PolicyDeactivated |
| Compliance | ComplianceReportGenerated, ViolationDetected, ViolationResolved |
CQRS Pipeline
sequenceDiagram
participant Controller
participant MediatR
participant LoggingBehavior
participant ValidatorBehavior
participant TransactionBehavior
participant CommandHandler
participant Repository
Controller->>MediatR: Send(Command)
MediatR->>LoggingBehavior: Handle
LoggingBehavior->>ValidatorBehavior: Next()
ValidatorBehavior->>TransactionBehavior: Next()
TransactionBehavior->>CommandHandler: Next()
CommandHandler->>Repository: Save
Repository-->>CommandHandler: Result
CommandHandler-->>Controller: Response
Pipeline Behaviors
| Order | Behavior | Purpose |
|---|---|---|
| 1 | LoggingBehavior | Log request/response with timing |
| 2 | ValidatorBehavior | FluentValidation |
| 3 | TransactionBehavior | Database transaction wrapper |
Security Architecture
graph TD
subgraph "Authentication"
JWT[JWT Bearer Tokens]
RS256[RS256 Signing]
OIDC[IdentityServer]
MFA[2FA/TOTP]
SOCIAL[Social OAuth]
end
subgraph "Authorization"
RBAC[Role-Based Access]
CLAIMS[Claims-Based]
POLICY[Policy Enforcement]
end
subgraph "Protection"
HASH[bcrypt Password Hash]
HTTPS[HTTPS/TLS]
CORS[CORS Policy]
EMAIL[Email Verification]
end
JWT --> RS256
RS256 --> OIDC
RBAC --> CLAIMS
CLAIMS --> POLICY
MFA --> JWT
SOCIAL --> JWT
style JWT fill:#3498db,stroke:#2980b9,color:#fff
style RBAC fill:#e74c3c,stroke:#c0392b,color:#fff
style HASH fill:#2ecc71,stroke:#27ae60,color:#fff
style MFA fill:#9b59b6,stroke:#7d3c98,color:#fff
style SOCIAL fill:#e67e22,stroke:#d35400,color:#fff
Authorization Policies
Overview
IAM Service uses Policy-Based Authorization to protect API endpoints:
graph TB
subgraph "Request Flow"
REQ[HTTP Request] --> AUTH[Authentication<br/>JWT Bearer]
AUTH --> POLICY[Policy Check]
POLICY --> HANDLER[Authorization Handler]
end
subgraph "Policies"
SUPER[RequireSuperAdmin]
ADMIN[RequireAdmin]
AUDITOR[RequireAuditor]
OWNER[OwnerOrAdmin]
end
subgraph "Roles"
R_SUPER[SuperAdmin]
R_ADMIN[Admin]
R_AUDITOR[Auditor]
R_USER[User]
end
HANDLER --> SUPER
HANDLER --> ADMIN
HANDLER --> AUDITOR
HANDLER --> OWNER
SUPER --> R_SUPER
ADMIN --> R_SUPER
ADMIN --> R_ADMIN
AUDITOR --> R_SUPER
AUDITOR --> R_ADMIN
AUDITOR --> R_AUDITOR
OWNER --> R_SUPER
OWNER --> R_ADMIN
OWNER --> R_USER
style SUPER fill:#c0392b,stroke:#922b21,color:#fff
style ADMIN fill:#e74c3c,stroke:#c0392b,color:#fff
style AUDITOR fill:#9b59b6,stroke:#7d3c98,color:#fff
style OWNER fill:#3498db,stroke:#2980b9,color:#fff
Policy Definitions
| Policy | Required Roles | Description |
|---|---|---|
RequireSuperAdmin |
SuperAdmin | Highest level - PAM, system config |
RequireAdmin |
SuperAdmin, Admin | User, role, organization management |
RequireAuditor |
SuperAdmin, Admin, Auditor | Audit logs, compliance reports |
OwnerOrAdmin |
Admin or resource owner | User self-service profile |
Authorization Flow
sequenceDiagram
participant Client
participant Controller
participant AuthorizationService
participant PolicyHandler
participant ClaimsPrincipal
Client->>Controller: Request with JWT
Controller->>AuthorizationService: Authorize(Policy)
AuthorizationService->>PolicyHandler: EvaluatePolicy()
PolicyHandler->>ClaimsPrincipal: GetRoles()
ClaimsPrincipal-->>PolicyHandler: ["Admin", "User"]
alt Role Match
PolicyHandler-->>AuthorizationService: Success
AuthorizationService-->>Controller: Authorized
Controller-->>Client: 200 OK
else Role Not Match
PolicyHandler-->>AuthorizationService: Fail
AuthorizationService-->>Controller: Forbidden
Controller-->>Client: 403 Forbidden
end
Controller Policy Mapping
| Controller | Policy | Endpoints |
|---|---|---|
| UsersController | RequireAdmin / OwnerOrAdmin | GET /users (Admin), GET/PUT /{id} (Owner or Admin) |
| RolesController | RequireAdmin | All endpoints |
| OrganizationsController | RequireAdmin | All endpoints |
| GroupsController | RequireAdmin | All endpoints |
| AccessRequestsController | RequireAdmin | All endpoints |
| AccessReviewsController | RequireAdmin | All endpoints |
| PrivilegedAccessController | RequireSuperAdmin | All endpoints (most sensitive) |
| AuditController | RequireAuditor | GET /audit/logs |
| ComplianceController | RequireAuditor | All endpoints |
| VerificationsController | RequireAdmin | All endpoints |
Email Verification Flow
sequenceDiagram
participant User
participant AuthController
participant EmailService
participant SMTP
participant Database
Note over User,Database: Send Verification Email
User->>AuthController: POST /send-verification-email
AuthController->>Database: Generate Token
Database-->>AuthController: Confirmation Token
AuthController->>EmailService: SendVerificationEmail()
EmailService->>SMTP: Send Email with Link
SMTP-->>User: Email with Verification Link
Note over User,Database: Confirm Email
User->>AuthController: POST /confirm-email<br/>(userId, token)
AuthController->>Database: Validate Token
Database-->>AuthController: Token Valid
AuthController->>Database: Set EmailConfirmed = true
AuthController-->>User: Email Confirmed
Two-Factor Authentication Flow
sequenceDiagram
participant User
participant AuthController
participant TwoFactorService
participant Database
participant AuthenticatorApp
Note over User,AuthenticatorApp: Enable 2FA
User->>AuthController: POST /2fa/enable
AuthController->>TwoFactorService: GenerateSecretKey()
TwoFactorService-->>AuthController: Secret Key
AuthController->>TwoFactorService: GenerateQrCode()
TwoFactorService-->>AuthController: QR Code (Base64)
AuthController-->>User: Secret + QR Code + Recovery Codes
User->>AuthenticatorApp: Scan QR Code
Note over User,AuthenticatorApp: Verify & Activate
User->>AuthController: POST /2fa/verify (code)
AuthController->>TwoFactorService: ValidateCode(secret, code)
TwoFactorService-->>AuthController: Valid
AuthController->>Database: Store Secret & Enable 2FA
AuthController-->>User: 2FA Enabled
Social Login Flow
sequenceDiagram
participant User
participant Client
participant AuthController
participant OAuthProvider
participant SocialLoginService
participant Database
Note over User,Database: OAuth Flow
User->>Client: Click "Login with Google"
Client->>AuthController: GET /external-login/Google
AuthController->>OAuthProvider: Redirect to OAuth
OAuthProvider->>User: Login & Consent
User->>OAuthProvider: Approve
OAuthProvider->>AuthController: GET /external-callback (code)
AuthController->>SocialLoginService: ProcessExternalLogin()
SocialLoginService->>Database: Find/Create User
Database-->>SocialLoginService: User
SocialLoginService-->>AuthController: User + Tokens
AuthController-->>Client: Redirect with tokens
Health Checks
graph TD
HC[Health Check Endpoints]
HC -->|/health/live| L[Liveness Probe]
HC -->|/health/ready| R[Readiness Probe]
HC -->|/health| F[Full Status]
R --> PG[(PostgreSQL Check)]
R --> RD[(Redis Check)]
style HC fill:#3498db,stroke:#2980b9,color:#fff
style L fill:#2ecc71,stroke:#27ae60,color:#fff
style R fill:#f39c12,stroke:#d68910,color:#fff
Deployment Architecture
Docker Compose (Local/Development)
services:
iam-service:
build: .
ports: ["5001:8080"]
depends_on:
- postgres
- redis
environment:
- DATABASE_URL=Host=postgres;...
postgres:
image: postgres:16-alpine
redis:
image: redis:7-alpine
Kubernetes (Production)
apiVersion: apps/v1
kind: Deployment
metadata:
name: iam-service
spec:
replicas: 3
template:
spec:
containers:
- name: iam-service
image: goodgo/iam-service:latest
ports:
- containerPort: 8080
livenessProbe:
httpGet:
path: /health/live
port: 8080
readinessProbe:
httpGet:
path: /health/ready
port: 8080
Error Handling
Exception Hierarchy
Exception
└── DomainException
└── (Custom domain exceptions)
Problem Details (RFC 7807)
All errors return Problem Details format:
{
"type": "https://tools.ietf.org/html/rfc7807",
"title": "Validation Error",
"status": 400,
"detail": "One or more validation errors occurred.",
"errors": {
"Email": ["Email is required"]
}
}
Distributed Caching Architecture
Caching Overview
graph TB
subgraph "Application"
SVC[Services]
CACHE[ICacheService]
end
subgraph "Caching Layer"
REDIS[(Redis Server)]
TOKEN[Token Cache]
SESSION[Session Cache]
DATA[Data Cache]
end
SVC --> CACHE
CACHE --> REDIS
REDIS --> TOKEN
REDIS --> SESSION
REDIS --> DATA
style CACHE fill:#e74c3c,stroke:#c0392b,color:#fff
style REDIS fill:#d35400,stroke:#a04000,color:#fff
style TOKEN fill:#9b59b6,stroke:#7d3c98,color:#fff
style SESSION fill:#3498db,stroke:#2980b9,color:#fff
style DATA fill:#2ecc71,stroke:#27ae60,color:#fff
ICacheService Interface
The service implements a generic ICacheService interface for distributed caching:
| Method | Purpose |
|---|---|
GetAsync<T> |
Retrieve cached item by key |
SetAsync<T> |
Store item with optional TTL |
RemoveAsync |
Delete cached item |
ExistsAsync |
Check if key exists |
GetOrSetAsync<T> |
Cache-aside pattern |
BlacklistAsync |
Add to token blacklist |
IsBlacklistedAsync |
Check token blacklist |
Token Caching Strategy
sequenceDiagram
participant Client
participant AuthController
participant CacheService
participant Redis
participant Database
Note over Client,Database: Token Validation with Cache
Client->>AuthController: Request with JWT
AuthController->>CacheService: IsBlacklistedAsync(tokenId)
CacheService->>Redis: GET blacklist:token:{id}
Redis-->>CacheService: null (not blacklisted)
CacheService-->>AuthController: false
AuthController->>AuthController: Validate JWT Claims
AuthController-->>Client: Response
Note over Client,Database: Token Revocation (Logout)
Client->>AuthController: POST /logout
AuthController->>CacheService: BlacklistAsync(tokenId, 7 days)
CacheService->>Redis: SETEX blacklist:token:{id} 604800 "1"
Redis-->>CacheService: OK
AuthController-->>Client: 200 OK
Token Cache Keys
| Key Pattern | Purpose | TTL |
|---|---|---|
blacklist:token:{tokenId} |
Revoked tokens | Token remaining lifetime |
blacklist:refresh:{tokenId} |
Revoked refresh tokens | 7 days |
user:{userId}:tokens |
User's active tokens | 15 minutes |
Session Caching Strategy
graph LR
subgraph "Session Data"
UID[User ID]
ROLES[User Roles]
PERMS[Permissions]
CLAIMS[Claims]
end
subgraph "Cache Keys"
K1[session:{userId}]
K2[user:{userId}:roles]
K3[user:{userId}:permissions]
end
UID --> K1
ROLES --> K2
PERMS --> K3
style K1 fill:#3498db,stroke:#2980b9,color:#fff
style K2 fill:#9b59b6,stroke:#7d3c98,color:#fff
style K3 fill:#2ecc71,stroke:#27ae60,color:#fff
Session Cache Keys
| Key Pattern | Purpose | TTL |
|---|---|---|
session:{userId} |
User session data | 30 minutes |
user:{userId}:roles |
Cached user roles | 15 minutes |
user:{userId}:permissions |
Computed permissions | 15 minutes |
user:{userId}:profile |
User profile data | 10 minutes |
Cache-Aside Pattern
sequenceDiagram
participant Service
participant CacheService
participant Redis
participant Database
Service->>CacheService: GetOrSetAsync(key, factory)
CacheService->>Redis: GET key
alt Cache Hit
Redis-->>CacheService: cached data
CacheService-->>Service: return data
else Cache Miss
Redis-->>CacheService: null
CacheService->>Database: factory() - fetch data
Database-->>CacheService: data
CacheService->>Redis: SETEX key ttl data
CacheService-->>Service: return data
end
Cache Invalidation Strategies
| Strategy | When to Use | Implementation |
|---|---|---|
| Time-based (TTL) | General data | SetAsync(..., TimeSpan.FromMinutes(15)) |
| Event-based | User updates | Remove cache on update event |
| Pattern-based | Bulk invalidation | RemoveByPatternAsync("user:*") |
Performance Considerations
- Connection Pooling: EF Core with Npgsql resilience
- Token Caching: Redis for token validation and blacklist
- Session Caching: User sessions and permissions cached
- Async Operations: All I/O operations are async
- Database Indexes: Configured in EntityConfigurations
- Cache-Aside Pattern: Reduce database load for frequent reads