Identity and Access Management Patterns in Cloud-Native Applications

Modern cloud-native applications face unprecedented challenges in managing user identities and controlling access to resources. The traditional perimeter-based security model has given way to sophisticated identity and access management (IAM) patterns that embrace the distributed nature of cloud architectures. Understanding these patterns is crucial for building secure, scalable applications that can adapt to evolving security requirements while maintaining excellent user experiences.

The Evolution of Identity Management

Cloud-native applications operate in environments where traditional network boundaries have dissolved. Users access applications from various devices and locations, while applications themselves consist of numerous microservices communicating across network boundaries. This distributed architecture demands identity management solutions that can provide consistent security policies across all components while maintaining the flexibility needed for modern development practices.

The shift toward cloud-native architectures has fundamentally changed how we approach identity and access management. Instead of relying on network-level controls, modern applications embed identity verification and authorization logic directly into their architecture. This approach ensures that every request is properly authenticated and authorized, regardless of its source or destination within the system.

// Modern identity provider integration using AWS Cognito
import { CognitoIdentityProviderClient, InitiateAuthCommand } from "@aws-sdk/client-cognito-identity-provider";
import { verify } from "jsonwebtoken";
import jwksClient from "jwks-rsa";

interface UserIdentity {
  userId: string;
  email: string;
  groups: string[];
  permissions: string[];
  tokenExpiry: Date;
}

export class CloudNativeIdentityProvider {
  private cognitoClient: CognitoIdentityProviderClient;
  private jwksClient: jwksClient.JwksClient;
  private userPoolId: string;
  private clientId: string;

  constructor(userPoolId: string, clientId: string, region: string) {
    this.cognitoClient = new CognitoIdentityProviderClient({ region });
    this.userPoolId = userPoolId;
    this.clientId = clientId;
    
    this.jwksClient = jwksClient({
      jwksUri: `https://cognito-idp.${region}.amazonaws.com/${userPoolId}/.well-known/jwks.json`,
      cache: true,
      cacheMaxAge: 600000, // 10 minutes
      rateLimit: true,
      jwksRequestsPerMinute: 10
    });
  }

  async authenticateUser(username: string, password: string): Promise<UserIdentity> {
    const command = new InitiateAuthCommand({
      AuthFlow: "USER_PASSWORD_AUTH",
      ClientId: this.clientId,
      AuthParameters: {
        USERNAME: username,
        PASSWORD: password
      }
    });

    try {
      const response = await this.cognitoClient.send(command);
      
      if (!response.AuthenticationResult?.AccessToken) {
        throw new Error("Authentication failed - no access token received");
      }

      return await this.validateAndParseToken(response.AuthenticationResult.AccessToken);
    } catch (error) {
      throw new Error(`Authentication failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
    }
  }

  private async validateAndParseToken(token: string): Promise<UserIdentity> {
    return new Promise((resolve, reject) => {
      const decoded = verify(token, this.getSigningKey.bind(this), {
        algorithms: ['RS256'],
        issuer: `https://cognito-idp.us-east-1.amazonaws.com/${this.userPoolId}`,
        audience: this.clientId
      }, (err, payload) => {
        if (err) {
          reject(new Error(`Token validation failed: ${err.message}`));
          return;
        }

        if (typeof payload === 'object' && payload !== null) {
          resolve({
            userId: payload.sub as string,
            email: payload.email as string,
            groups: payload['cognito:groups'] as string[] || [],
            permissions: this.extractPermissions(payload),
            tokenExpiry: new Date((payload.exp as number) * 1000)
          });
        } else {
          reject(new Error("Invalid token payload"));
        }
      });
    });
  }

  private async getSigningKey(header: any, callback: any) {
    this.jwksClient.getSigningKey(header.kid, (err, key) => {
      if (err) {
        callback(err);
        return;
      }
      const signingKey = key?.getPublicKey();
      callback(null, signingKey);
    });
  }

  private extractPermissions(payload: any): string[] {
    const groups = payload['cognito:groups'] as string[] || [];
    const scopes = payload.scope?.split(' ') || [];
    return [...groups.map(group => `group:${group}`), ...scopes];
  }
}

Attribute-Based Access Control (ABAC)

Traditional role-based access control (RBAC) systems often prove too rigid for cloud-native applications. Attribute-based access control represents a more flexible approach that considers multiple factors when making authorization decisions. ABAC evaluates user attributes, resource attributes, environmental conditions, and contextual information to determine whether access should be granted.

The power of ABAC lies in its ability to make nuanced decisions based on dynamic conditions. A user might have different levels of access depending on their location, the time of day, the sensitivity of the data they’re requesting, or the security posture of their device. This granular control is essential for applications that handle sensitive data or operate in regulated environments.

// Advanced ABAC implementation for cloud-native applications
interface AccessRequest {
  subject: SubjectAttributes;
  resource: ResourceAttributes;
  action: string;
  environment: EnvironmentAttributes;
}

interface SubjectAttributes {
  userId: string;
  roles: string[];
  department: string;
  clearanceLevel: number;
  location: GeographicLocation;
  deviceTrustScore: number;
}

interface ResourceAttributes {
  resourceId: string;
  classification: DataClassification;
  owner: string;
  dataTypes: string[];
  sensitivity: number;
}

interface EnvironmentAttributes {
  timestamp: Date;
  sourceIP: string;
  networkZone: SecurityZone;
  timeOfDay: number;
  isBusinessHours: boolean;
}

enum DataClassification {
  PUBLIC = 'PUBLIC',
  INTERNAL = 'INTERNAL',
  CONFIDENTIAL = 'CONFIDENTIAL',
  RESTRICTED = 'RESTRICTED'
}

enum SecurityZone {
  TRUSTED = 'TRUSTED',
  CORPORATE = 'CORPORATE',
  DMZ = 'DMZ',
  UNTRUSTED = 'UNTRUSTED'
}

interface GeographicLocation {
  country: string;
  region: string;
  city: string;
  coordinates: {
    latitude: number;
    longitude: number;
  };
}

export class ABACEngine {
  private policies: AccessPolicy[];
  private attributeProvider: AttributeProvider;

  constructor(attributeProvider: AttributeProvider) {
    this.attributeProvider = attributeProvider;
    this.policies = this.loadPolicies();
  }

  async authorize(request: AccessRequest): Promise<AuthorizationResult> {
    // Enrich request with additional attributes
    const enrichedRequest = await this.enrichRequest(request);
    
    // Evaluate all applicable policies
    const policyResults = await Promise.all(
      this.policies
        .filter(policy => this.isPolicyApplicable(policy, enrichedRequest))
        .map(policy => this.evaluatePolicy(policy, enrichedRequest))
    );

    // Combine results using policy combining algorithm
    return this.combineResults(policyResults, enrichedRequest);
  }

  private async enrichRequest(request: AccessRequest): Promise<AccessRequest> {
    // Enhance subject attributes with real-time data
    const enhancedSubject = await this.attributeProvider.enrichSubjectAttributes(request.subject);
    
    // Add resource metadata
    const enhancedResource = await this.attributeProvider.enrichResourceAttributes(request.resource);
    
    // Calculate environmental context
    const enhancedEnvironment = await this.attributeProvider.enrichEnvironmentAttributes(request.environment);

    return {
      ...request,
      subject: enhancedSubject,
      resource: enhancedResource,
      environment: enhancedEnvironment
    };
  }

  private async evaluatePolicy(policy: AccessPolicy, request: AccessRequest): Promise<PolicyResult> {
    try {
      // Check if user meets minimum requirements
      if (!this.checkMinimumRequirements(policy, request)) {
        return { decision: 'DENY', reason: 'Minimum requirements not met', policy: policy.id };
      }

      // Evaluate business rules
      const businessRuleResult = await this.evaluateBusinessRules(policy.rules, request);
      if (!businessRuleResult.passed) {
        return { decision: 'DENY', reason: businessRuleResult.reason, policy: policy.id };
      }

      // Check temporal constraints
      if (!this.checkTemporalConstraints(policy.temporalConstraints, request.environment)) {
        return { decision: 'DENY', reason: 'Temporal constraints violated', policy: policy.id };
      }

      // Evaluate risk factors
      const riskScore = await this.calculateRiskScore(request);
      if (riskScore > policy.maxRiskThreshold) {
        return { decision: 'DENY', reason: `Risk score ${riskScore} exceeds threshold`, policy: policy.id };
      }

      return { decision: 'PERMIT', reason: 'Policy evaluation successful', policy: policy.id };
    } catch (error) {
      return { decision: 'INDETERMINATE', reason: `Policy evaluation error: ${error}`, policy: policy.id };
    }
  }

  private async calculateRiskScore(request: AccessRequest): Promise<number> {
    let riskScore = 0;

    // Location-based risk
    if (request.environment.networkZone === SecurityZone.UNTRUSTED) {
      riskScore += 30;
    }

    // Time-based risk
    if (!request.environment.isBusinessHours) {
      riskScore += 15;
    }

    // Device trust risk
    if (request.subject.deviceTrustScore < 0.7) {
      riskScore += 25;
    }

    // Data sensitivity risk
    if (request.resource.sensitivity > 7) {
      riskScore += 20;
    }

    // Cross-border access risk
    const userLocation = request.subject.location;
    const resourceLocation = await this.attributeProvider.getResourceLocation(request.resource.resourceId);
    if (userLocation.country !== resourceLocation.country) {
      riskScore += 10;
    }

    return Math.min(riskScore, 100); // Cap at 100
  }

  private checkMinimumRequirements(policy: AccessPolicy, request: AccessRequest): boolean {
    // Check clearance level
    if (request.subject.clearanceLevel < policy.minimumClearance) {
      return false;
    }

    // Check required roles
    const hasRequiredRole = policy.requiredRoles.some(role => 
      request.subject.roles.includes(role)
    );
    if (!hasRequiredRole) {
      return false;
    }

    // Check data classification compatibility
    const subjectMaxClassification = this.getMaxClassificationForUser(request.subject);
    if (this.compareClassifications(request.resource.classification, subjectMaxClassification) > 0) {
      return false;
    }

    return true;
  }

  private checkTemporalConstraints(constraints: TemporalConstraint[], environment: EnvironmentAttributes): boolean {
    return constraints.every(constraint => {
      switch (constraint.type) {
        case 'TIME_OF_DAY':
          return environment.timeOfDay >= constraint.startHour && environment.timeOfDay <= constraint.endHour;
        case 'BUSINESS_HOURS':
          return environment.isBusinessHours === constraint.required;
        case 'EXPIRY':
          return environment.timestamp <= constraint.expiryDate;
        default:
          return true;
      }
    });
  }

  private combineResults(results: PolicyResult[], request: AccessRequest): AuthorizationResult {
    // Implement deny-overrides combining algorithm
    const denyResults = results.filter(r => r.decision === 'DENY');
    if (denyResults.length > 0) {
      return {
        decision: 'DENY',
        reason: denyResults[0].reason,
        riskScore: 0,
        additionalMeasures: []
      };
    }

    const permitResults = results.filter(r => r.decision === 'PERMIT');
    if (permitResults.length > 0) {
      return {
        decision: 'PERMIT',
        reason: 'Access granted by applicable policies',
        riskScore: 0,
        additionalMeasures: this.determineAdditionalMeasures(request)
      };
    }

    return {
      decision: 'DENY',
      reason: 'No applicable policies found',
      riskScore: 0,
      additionalMeasures: []
    };
  }

  private determineAdditionalMeasures(request: AccessRequest): SecurityMeasure[] {
    const measures: SecurityMeasure[] = [];

    // Require MFA for sensitive operations
    if (request.resource.sensitivity > 8) {
      measures.push({
        type: 'MFA_REQUIRED',
        description: 'Multi-factor authentication required for highly sensitive resource'
      });
    }

    // Add monitoring for unusual access patterns
    if (request.environment.networkZone !== SecurityZone.TRUSTED) {
      measures.push({
        type: 'ENHANCED_MONITORING',
        description: 'Enhanced monitoring enabled for non-trusted network access'
      });
    }

    return measures;
  }
}

Just-In-Time Access Patterns

Just-in-time (JIT) access represents a paradigm shift from permanent privilege assignment to temporary, context-aware access grants. This approach significantly reduces the attack surface by ensuring that users only have access to resources when they specifically need them and only for the duration required to complete their tasks.

Implementing JIT access requires sophisticated orchestration of identity providers, approval workflows, and automated provisioning systems. The architecture must be capable of rapidly granting access based on predefined criteria while maintaining comprehensive audit trails and automatic revocation mechanisms.

// Just-in-time access orchestration system
interface AccessRequest {
  requestId: string;
  userId: string;
  resourceId: string;
  justification: string;
  requestedDuration: number; // in minutes
  urgency: UrgencyLevel;
  businessJustification: string;
}

enum UrgencyLevel {
  LOW = 'LOW',
  MEDIUM = 'MEDIUM',
  HIGH = 'HIGH',
  CRITICAL = 'CRITICAL'
}

interface ApprovalWorkflow {
  workflowId: string;
  requiredApprovers: string[];
  autoApprovalCriteria?: AutoApprovalCriteria;
  escalationRules: EscalationRule[];
}

interface AutoApprovalCriteria {
  maxDuration: number;
  allowedResources: string[];
  allowedUsers: string[];
  timeWindows: TimeWindow[];
}

export class JITAccessOrchestrator {
  private accessRequests: Map<string, AccessRequest> = new Map();
  private activeGrants: Map<string, ActiveGrant> = new Map();
  private approvalEngine: ApprovalEngine;
  private provisioningService: ProvisioningService;
  private auditLogger: AuditLogger;

  constructor(
    approvalEngine: ApprovalEngine,
    provisioningService: ProvisioningService,
    auditLogger: AuditLogger
  ) {
    this.approvalEngine = approvalEngine;
    this.provisioningService = provisioningService;
    this.auditLogger = auditLogger;

    // Start background cleanup process
    this.startCleanupProcess();
  }

  async requestAccess(request: AccessRequest): Promise<JITResponse> {
    // Validate request
    const validation = await this.validateRequest(request);
    if (!validation.isValid) {
      return {
        status: 'REJECTED',
        reason: validation.reason,
        requestId: request.requestId
      };
    }

    // Store request
    this.accessRequests.set(request.requestId, request);

    // Log request
    await this.auditLogger.logAccessRequest(request);

    // Determine approval workflow
    const workflow = await this.determineWorkflow(request);

    // Check for auto-approval
    if (await this.checkAutoApproval(request, workflow)) {
      return await this.autoApproveAccess(request);
    }

    // Initiate approval process
    const approvalResult = await this.approvalEngine.initiateApproval(request, workflow);
    
    return {
      status: 'PENDING_APPROVAL',
      reason: 'Request submitted for approval',
      requestId: request.requestId,
      expectedApprovalTime: approvalResult.estimatedTime,
      approvers: approvalResult.requiredApprovers
    };
  }

  async approveAccess(requestId: string, approverId: string, comments?: string): Promise<JITResponse> {
    const request = this.accessRequests.get(requestId);
    if (!request) {
      return {
        status: 'REJECTED',
        reason: 'Request not found',
        requestId
      };
    }

    // Process approval
    const approvalResult = await this.approvalEngine.processApproval(requestId, approverId, true, comments);
    
    if (approvalResult.isComplete && approvalResult.isApproved) {
      return await this.grantAccess(request);
    }

    return {
      status: 'PENDING_APPROVAL',
      reason: 'Additional approvals required',
      requestId,
      pendingApprovers: approvalResult.pendingApprovers
    };
  }

  private async autoApproveAccess(request: AccessRequest): Promise<JITResponse> {
    try {
      const grant = await this.grantAccess(request);
      await this.auditLogger.logAutoApproval(request, 'Auto-approval criteria met');
      return grant;
    } catch (error) {
      await this.auditLogger.logError(request.requestId, `Auto-approval failed: ${error}`);
      throw error;
    }
  }

  private async grantAccess(request: AccessRequest): Promise<JITResponse> {
    try {
      // Calculate actual grant duration (may be less than requested)
      const grantDuration = await this.calculateGrantDuration(request);
      const expiryTime = new Date(Date.now() + grantDuration * 60000);

      // Provision access
      const provisioningResult = await this.provisioningService.grantAccess({
        userId: request.userId,
        resourceId: request.resourceId,
        permissions: await this.determinePermissions(request),
        expiryTime
      });

      // Create active grant record
      const activeGrant: ActiveGrant = {
        grantId: provisioningResult.grantId,
        requestId: request.requestId,
        userId: request.userId,
        resourceId: request.resourceId,
        grantedAt: new Date(),
        expiresAt: expiryTime,
        permissions: provisioningResult.permissions,
        autoRevoke: true
      };

      this.activeGrants.set(activeGrant.grantId, activeGrant);

      // Schedule automatic revocation
      this.scheduleRevocation(activeGrant);

      // Log successful grant
      await this.auditLogger.logAccessGrant(activeGrant);

      return {
        status: 'GRANTED',
        reason: 'Access successfully granted',
        requestId: request.requestId,
        grantId: activeGrant.grantId,
        expiresAt: expiryTime,
        permissions: provisioningResult.permissions
      };

    } catch (error) {
      await this.auditLogger.logError(request.requestId, `Access grant failed: ${error}`);
      return {
        status: 'REJECTED',
        reason: `Failed to grant access: ${error}`,
        requestId: request.requestId
      };
    }
  }

  private async calculateGrantDuration(request: AccessRequest): Promise<number> {
    // Get policy-defined maximum duration for this resource
    const maxDuration = await this.getMaxDurationForResource(request.resourceId);
    
    // Consider urgency level
    const urgencyMultiplier = this.getUrgencyMultiplier(request.urgency);
    const adjustedMaxDuration = maxDuration * urgencyMultiplier;

    // Return the minimum of requested duration and policy maximum
    return Math.min(request.requestedDuration, adjustedMaxDuration);
  }

  private getUrgencyMultiplier(urgency: UrgencyLevel): number {
    switch (urgency) {
      case UrgencyLevel.LOW: return 0.5;
      case UrgencyLevel.MEDIUM: return 0.75;
      case UrgencyLevel.HIGH: return 1.0;
      case UrgencyLevel.CRITICAL: return 1.5;
      default: return 0.75;
    }
  }

  private scheduleRevocation(grant: ActiveGrant): void {
    const timeUntilExpiry = grant.expiresAt.getTime() - Date.now();
    
    setTimeout(async () => {
      await this.revokeAccess(grant.grantId, 'Automatic expiration');
    }, timeUntilExpiry);
  }

  async revokeAccess(grantId: string, reason: string): Promise<void> {
    const grant = this.activeGrants.get(grantId);
    if (!grant) {
      throw new Error(`Grant ${grantId} not found`);
    }

    try {
      // Revoke access in the target system
      await this.provisioningService.revokeAccess(grantId);

      // Remove from active grants
      this.activeGrants.delete(grantId);

      // Log revocation
      await this.auditLogger.logAccessRevocation(grant, reason);

    } catch (error) {
      await this.auditLogger.logError(grantId, `Revocation failed: ${error}`);
      throw error;
    }
  }

  private startCleanupProcess(): void {
    // Run cleanup every 5 minutes
    setInterval(async () => {
      const now = new Date();
      const expiredGrants = Array.from(this.activeGrants.values())
        .filter(grant => grant.expiresAt <= now);

      for (const grant of expiredGrants) {
        try {
          await this.revokeAccess(grant.grantId, 'Cleanup process - expired grant');
        } catch (error) {
          console.error(`Failed to cleanup expired grant ${grant.grantId}:`, error);
        }
      }
    }, 5 * 60 * 1000); // 5 minutes
  }

  async getActiveGrants(userId?: string): Promise<ActiveGrant[]> {
    const grants = Array.from(this.activeGrants.values());
    return userId ? grants.filter(grant => grant.userId === userId) : grants;
  }

  async extendAccess(grantId: string, additionalMinutes: number, justification: string): Promise<JITResponse> {
    const grant = this.activeGrants.get(grantId);
    if (!grant) {
      return {
        status: 'REJECTED',
        reason: 'Grant not found',
        requestId: grantId
      };
    }

    // Check if extension is allowed
    const maxExtension = await this.getMaxExtensionForResource(grant.resourceId);
    if (additionalMinutes > maxExtension) {
      return {
        status: 'REJECTED',
        reason: `Extension exceeds maximum allowed duration of ${maxExtension} minutes`,
        requestId: grantId
      };
    }

    // Extend the grant
    const newExpiryTime = new Date(grant.expiresAt.getTime() + additionalMinutes * 60000);
    grant.expiresAt = newExpiryTime;

    // Update the provisioning system
    await this.provisioningService.extendAccess(grantId, newExpiryTime);

    // Log the extension
    await this.auditLogger.logAccessExtension(grant, additionalMinutes, justification);

    return {
      status: 'GRANTED',
      reason: 'Access successfully extended',
      requestId: grantId,
      expiresAt: newExpiryTime
    };
  }
}

Token-Based Authentication Strategies

Modern cloud-native applications rely heavily on token-based authentication mechanisms that provide stateless, scalable identity verification. JSON Web Tokens (JWT) have become the de facto standard for representing claims securely between parties, but their implementation requires careful consideration of security best practices and architectural patterns.

The evolution of token strategies has moved beyond simple bearer tokens to sophisticated patterns that include refresh token rotation, proof-of-possession tokens, and context-aware token validation. These advanced patterns provide enhanced security while maintaining the performance and scalability benefits that make tokens attractive for distributed systems.

// Advanced token management system with security best practices
interface TokenClaims {
  sub: string; // Subject (user ID)
  iss: string; // Issuer
  aud: string; // Audience
  exp: number; // Expiration time
  iat: number; // Issued at
  jti: string; // JWT ID (unique identifier)
  scope: string; // Permissions scope
  device_id?: string; // Device identifier
  session_id?: string; // Session identifier
  risk_score?: number; // Risk assessment score
}

interface TokenPair {
  accessToken: string;
  refreshToken: string;
  tokenType: string;
  expiresIn: number;
  scope: string;
}

interface DeviceInfo {
  deviceId: string;
  deviceType: string;
  platform: string;
  appVersion: string;
  deviceFingerprint: string;
}

export class SecureTokenManager {
  private readonly accessTokenTTL = 15 * 60; // 15 minutes
  private readonly refreshTokenTTL = 7 * 24 * 60 * 60; // 7 days
  private readonly maxRefreshTokens = 5; // Per user
  private tokenBlacklist: Set<string> = new Set();
  private refreshTokenStore: Map<string, RefreshTokenInfo> = new Map();

  constructor(
    private jwtSecret: string,
    private issuer: string,
    private audience: string,
    private encryptionKey: string
  ) {
    // Start cleanup process for expired tokens
    this.startTokenCleanup();
  }

  async generateTokenPair(
    userId: string, 
    scope: string, 
    deviceInfo: DeviceInfo,
    riskScore: number = 0
  ): Promise<TokenPair> {
    // Generate unique identifiers
    const jti = this.generateSecureId();
    const sessionId = this.generateSecureId();
    const refreshTokenId = this.generateSecureId();

    // Create access token claims
    const accessTokenClaims: TokenClaims = {
      sub: userId,
      iss: this.issuer,
      aud: this.audience,
      exp: Math.floor(Date.now() / 1000) + this.accessTokenTTL,
      iat: Math.floor(Date.now() / 1000),
      jti,
      scope,
      device_id: deviceInfo.deviceId,
      session_id: sessionId,
      risk_score: riskScore
    };

    // Generate access token
    const accessToken = await this.createSignedToken(accessTokenClaims);

    // Create refresh token with encrypted payload
    const refreshTokenPayload = {
      user_id: userId,
      session_id: sessionId,
      device_id: deviceInfo.deviceId,
      scope,
      exp: Math.floor(Date.now() / 1000) + this.refreshTokenTTL,
      jti: refreshTokenId
    };

    const refreshToken = await this.createEncryptedRefreshToken(refreshTokenPayload);

    // Store refresh token metadata
    await this.storeRefreshToken(refreshTokenId, {
      userId,
      deviceInfo,
      sessionId,
      createdAt: new Date(),
      expiresAt: new Date(Date.now() + this.refreshTokenTTL * 1000),
      lastUsed: new Date(),
      isActive: true
    });

    // Cleanup old refresh tokens for this user/device
    await this.cleanupOldRefreshTokens(userId, deviceInfo.deviceId);

    return {
      accessToken,
      refreshToken,
      tokenType: 'Bearer',
      expiresIn: this.accessTokenTTL,
      scope
    };
  }

  async validateAccessToken(token: string, requiredScope?: string): Promise<TokenValidationResult> {
    try {
      // Check if token is blacklisted
      if (this.tokenBlacklist.has(token)) {
        return {
          isValid: false,
          reason: 'Token has been revoked',
          claims: null
        };
      }

      // Verify and decode token
      const claims = await this.verifySignedToken(token);

      // Additional security checks
      const securityChecks = await this.performSecurityChecks(claims, token);
      if (!securityChecks.passed) {
        return {
          isValid: false,
          reason: securityChecks.reason,
          claims: null
        };
      }

      // Check scope if required
      if (requiredScope && !this.hasRequiredScope(claims.scope, requiredScope)) {
        return {
          isValid: false,
          reason: 'Insufficient scope',
          claims: null
        };
      }

      return {
        isValid: true,
        reason: 'Token is valid',
        claims
      };

    } catch (error) {
      return {
        isValid: false,
        reason: `Token validation failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
        claims: null
      };
    }
  }

  async refreshTokenPair(refreshToken: string, deviceInfo: DeviceInfo): Promise<TokenPair> {
    try {
      // Decrypt and validate refresh token
      const refreshPayload = await this.decryptRefreshToken(refreshToken);
      
      // Get refresh token info from store
      const tokenInfo = this.refreshTokenStore.get(refreshPayload.jti);
      if (!tokenInfo || !tokenInfo.isActive) {
        throw new Error('Refresh token not found or inactive');
      }

      // Validate device consistency
      if (tokenInfo.deviceInfo.deviceId !== deviceInfo.deviceId) {
        // Device mismatch - possible token theft
        await this.revokeAllUserTokens(tokenInfo.userId, 'Device mismatch detected');
        throw new Error('Device validation failed');
      }

      // Check if refresh token is expired
      if (tokenInfo.expiresAt <= new Date()) {
        throw new Error('Refresh token expired');
      }

      // Update last used timestamp
      tokenInfo.lastUsed = new Date();

      // Calculate new risk score based on usage patterns
      const riskScore = await this.calculateRefreshRiskScore(tokenInfo, deviceInfo);

      // Generate new token pair
      const newTokenPair = await this.generateTokenPair(
        tokenInfo.userId,
        refreshPayload.scope,
        deviceInfo,
        riskScore
      );

      // Optionally rotate refresh token for high-risk scenarios
      if (riskScore > 70) {
        await this.revokeRefreshToken(refreshPayload.jti);
      }

      return newTokenPair;

    } catch (error) {
      throw new Error(`Token refresh failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
    }
  }

  private async performSecurityChecks(claims: TokenClaims, token: string): Promise<SecurityCheckResult> {
    // Check token age
    const tokenAge = Date.now() / 1000 - claims.iat;
    if (tokenAge > this.accessTokenTTL) {
      return { passed: false, reason: 'Token expired' };
    }

    // Risk score validation
    if (claims.risk_score && claims.risk_score > 90) {
      return { passed: false, reason: 'Risk score too high' };
    }

    // Check for session validity if session management is enabled
    if (claims.session_id) {
      const sessionValid = await this.validateSession(claims.session_id, claims.sub);
      if (!sessionValid) {
        return { passed: false, reason: 'Session invalid' };
      }
    }

    // Device binding validation
    if (claims.device_id) {
      const deviceValid = await this.validateDeviceBinding(claims.device_id, claims.sub);
      if (!deviceValid) {
        return { passed: false, reason: 'Device binding invalid' };
      }
    }

    return { passed: true, reason: 'All security checks passed' };
  }

  private async calculateRefreshRiskScore(tokenInfo: RefreshTokenInfo, deviceInfo: DeviceInfo): Promise<number> {
    let riskScore = 0;

    // Time-based risk factors
    const timeSinceLastUse = Date.now() - tokenInfo.lastUsed.getTime();
    if (timeSinceLastUse > 24 * 60 * 60 * 1000) { // More than 24 hours
      riskScore += 20;
    }

    // Device fingerprint changes
    if (tokenInfo.deviceInfo.deviceFingerprint !== deviceInfo.deviceFingerprint) {
      riskScore += 30;
    }

    // Platform/app version changes
    if (tokenInfo.deviceInfo.platform !== deviceInfo.platform) {
      riskScore += 40;
    }

    if (tokenInfo.deviceInfo.appVersion !== deviceInfo.appVersion) {
      riskScore += 10;
    }

    // Frequency analysis
    const refreshFrequency = await this.analyzeRefreshFrequency(tokenInfo.userId);
    if (refreshFrequency > 10) { // More than 10 refreshes per hour
      riskScore += 25;
    }

    return Math.min(riskScore, 100);
  }

  async revokeToken(token: string): Promise<void> {
    // Add to blacklist
    this.tokenBlacklist.add(token);

    // If it's a refresh token, mark as inactive
    try {
      const refreshPayload = await this.decryptRefreshToken(token);
      const tokenInfo = this.refreshTokenStore.get(refreshPayload.jti);
      if (tokenInfo) {
        tokenInfo.isActive = false;
      }
    } catch {
      // Not a refresh token, blacklisting is sufficient
    }
  }

  async revokeAllUserTokens(userId: string, reason: string): Promise<void> {
    // Find all refresh tokens for the user
    const userTokens = Array.from(this.refreshTokenStore.entries())
      .filter(([_, info]) => info.userId === userId);

    // Mark all as inactive
    for (const [tokenId, info] of userTokens) {
      info.isActive = false;
    }

    // Log security event
    console.warn(`Revoked all tokens for user ${userId}: ${reason}`);
  }

  private startTokenCleanup(): void {
    // Run cleanup every hour
    setInterval(() => {
      this.cleanupExpiredTokens();
    }, 60 * 60 * 1000);
  }

  private cleanupExpiredTokens(): void {
    const now = new Date();
    
    // Clean up expired refresh tokens
    for (const [tokenId, info] of this.refreshTokenStore.entries()) {
      if (info.expiresAt <= now) {
        this.refreshTokenStore.delete(tokenId);
      }
    }

    // Clean up old blacklisted access tokens (they expire anyway)
    // This would require more sophisticated tracking in a real implementation
  }

  private async cleanupOldRefreshTokens(userId: string, deviceId: string): Promise<void> {
    const userDeviceTokens = Array.from(this.refreshTokenStore.entries())
      .filter(([_, info]) => info.userId === userId && info.deviceInfo.deviceId === deviceId)
      .sort(([_, a], [__, b]) => b.createdAt.getTime() - a.createdAt.getTime());

    // Keep only the most recent tokens up to the limit
    const tokensToRemove = userDeviceTokens.slice(this.maxRefreshTokens);
    for (const [tokenId] of tokensToRemove) {
      this.refreshTokenStore.delete(tokenId);
    }
  }
}

Conclusion

Identity and access management in cloud-native applications represents a fundamental shift from traditional security models to dynamic, context-aware systems that can adapt to changing threats and business requirements. The patterns explored in this post demonstrate how modern applications can implement sophisticated security controls while maintaining the flexibility and scalability that cloud-native architectures demand.

The implementation of these IAM patterns requires careful consideration of the trade-offs between security, usability, and performance. Attribute-based access control provides the granular control needed for complex authorization decisions but introduces complexity in policy management and evaluation. Just-in-time access patterns significantly reduce security risks by minimizing standing privileges but require robust orchestration and approval systems. Advanced token strategies enhance security through multiple layers of validation and risk assessment while maintaining the stateless nature essential for distributed systems.

As cloud-native applications continue to evolve, IAM systems must adapt to support new architectural patterns, emerging threats, and changing regulatory requirements. Organizations implementing these patterns should focus on creating flexible, extensible systems that can grow with their security needs while providing comprehensive audit capabilities and seamless user experiences. The foundation laid by these identity and access management patterns will prove essential as we explore additional security layers in subsequent posts in this series.

Comments