14 Authentication und Authorization

Authentication und Authorization sind fundamentale Sicherheitsaspekte jeder modernen Webanwendung. NestJS bietet robuste Tools und Patterns für die Implementierung sicherer Authentifizierungssysteme. In diesem Kapitel behandeln wir moderne Authentifizierungsstrategien, JWT-Implementation, Role-based Access Control und fortgeschrittene Sicherheitskonzepte.

14.1 Moderne Authentifizierungsstrategien

Die Landschaft der Authentifizierung hat sich in den letzten Jahren erheblich weiterentwickelt. Moderne Anwendungen müssen verschiedene Authentifizierungsmethoden unterstützen und gleichzeitig höchste Sicherheitsstandards einhalten.

14.1.1 Überblick der Authentifizierungsarten

14.1.1.1 Traditional Session-Based Authentication

14.1.1.2 Token-Based Authentication (JWT)

14.1.1.3 OAuth 2.0 / OpenID Connect

14.1.1.4 Multi-Factor Authentication (MFA)

14.1.2 Authentication vs. Authorization

Authentication (Authentifizierung): “Wer bist du?” - Verifizierung der Identität - Login-Prozess - Credential-Validation

Authorization (Autorisierung): “Was darfst du?” - Zugriffskontrolle - Berechtigung für Ressourcen - Role-based oder Attribute-based Access Control

14.2 Passport.js-Integration

Passport.js ist die de-facto Standard-Bibliothek für Authentication in Node.js-Anwendungen. NestJS bietet eine nahtlose Integration mit über 500 verfügbaren Strategien.

14.2.1 Installation und Grundkonfiguration

npm install @nestjs/passport passport
npm install @nestjs/jwt passport-jwt
npm install passport-local
npm install @types/passport-local @types/passport-jwt

14.2.2 Passport Module Setup

import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { LocalStrategy } from './strategies/local.strategy';
import { JwtStrategy } from './strategies/jwt.strategy';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { UsersModule } from '../users/users.module';

@Module({
  imports: [
    UsersModule,
    PassportModule.register({ defaultStrategy: 'jwt' }),
    JwtModule.registerAsync({
      imports: [ConfigModule],
      useFactory: async (configService: ConfigService) => ({
        secret: configService.get<string>('JWT_SECRET'),
        signOptions: {
          expiresIn: configService.get<string>('JWT_EXPIRES_IN', '1h'),
          issuer: configService.get<string>('JWT_ISSUER', 'nestjs-app'),
          audience: configService.get<string>('JWT_AUDIENCE', 'nestjs-users'),
        },
      }),
      inject: [ConfigService],
    }),
  ],
  providers: [AuthService, LocalStrategy, JwtStrategy],
  controllers: [AuthController],
  exports: [AuthService, PassportModule, JwtModule],
})
export class AuthModule {}

14.2.3 Local Strategy

Die Local Strategy wird für die traditionelle Username/Password-Authentifizierung verwendet.

import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-local';
import { AuthService } from '../auth.service';
import { User } from '../entities/user.entity';

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
  constructor(private authService: AuthService) {
    super({
      usernameField: 'email', // Standard ist 'username'
      passwordField: 'password',
      passReqToCallback: false,
    });
  }

  async validate(email: string, password: string): Promise<User> {
    const user = await this.authService.validateUser(email, password);
    
    if (!user) {
      throw new UnauthorizedException('Invalid credentials');
    }

    if (!user.isActive) {
      throw new UnauthorizedException('Account is deactivated');
    }

    if (user.lockedUntil && user.lockedUntil > new Date()) {
      throw new UnauthorizedException('Account is temporarily locked');
    }

    return user;
  }
}

14.2.4 JWT Strategy

Die JWT Strategy validiert Bearer Token und extrahiert Benutzerinformationen.

import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { ConfigService } from '@nestjs/config';
import { AuthService } from '../auth.service';
import { JwtPayload } from '../interfaces/jwt-payload.interface';
import { User } from '../entities/user.entity';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(
    private authService: AuthService,
    private configService: ConfigService,
  ) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: configService.get<string>('JWT_SECRET'),
      issuer: configService.get<string>('JWT_ISSUER'),
      audience: configService.get<string>('JWT_AUDIENCE'),
    });
  }

  async validate(payload: JwtPayload): Promise<User> {
    const user = await this.authService.findUserById(payload.sub);
    
    if (!user) {
      throw new UnauthorizedException('User not found');
    }

    if (!user.isActive) {
      throw new UnauthorizedException('User account is deactivated');
    }

    // Prüfung auf Token-Revocation (optional)
    if (payload.jti && await this.authService.isTokenRevoked(payload.jti)) {
      throw new UnauthorizedException('Token has been revoked');
    }

    // Prüfung auf Passwort-Änderung
    if (payload.iat && user.passwordChangedAt) {
      const passwordChangedTimestamp = Math.floor(user.passwordChangedAt.getTime() / 1000);
      if (payload.iat < passwordChangedTimestamp) {
        throw new UnauthorizedException('Password has been changed. Please login again.');
      }
    }

    return user;
  }
}

14.2.5 OAuth2 Strategies

OAuth2-Integration für externe Provider wie Google, GitHub, etc.

14.2.5.1 Google OAuth2 Strategy

import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy, VerifyCallback } from 'passport-google-oauth20';
import { ConfigService } from '@nestjs/config';
import { AuthService } from '../auth.service';

@Injectable()
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
  constructor(
    private authService: AuthService,
    private configService: ConfigService,
  ) {
    super({
      clientID: configService.get<string>('GOOGLE_CLIENT_ID'),
      clientSecret: configService.get<string>('GOOGLE_CLIENT_SECRET'),
      callbackURL: configService.get<string>('GOOGLE_CALLBACK_URL'),
      scope: ['email', 'profile'],
    });
  }

  async validate(
    accessToken: string,
    refreshToken: string,
    profile: any,
    done: VerifyCallback,
  ): Promise<any> {
    const { name, emails, photos } = profile;
    
    const user = await this.authService.findOrCreateOAuthUser({
      email: emails[0].value,
      firstName: name.givenName,
      lastName: name.familyName,
      avatar: photos[0].value,
      provider: 'google',
      providerId: profile.id,
      accessToken,
      refreshToken,
    });

    done(null, user);
  }
}

14.2.5.2 GitHub OAuth2 Strategy

import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-github2';
import { ConfigService } from '@nestjs/config';
import { AuthService } from '../auth.service';

@Injectable()
export class GitHubStrategy extends PassportStrategy(Strategy, 'github') {
  constructor(
    private authService: AuthService,
    private configService: ConfigService,
  ) {
    super({
      clientID: configService.get<string>('GITHUB_CLIENT_ID'),
      clientSecret: configService.get<string>('GITHUB_CLIENT_SECRET'),
      callbackURL: configService.get<string>('GITHUB_CALLBACK_URL'),
      scope: ['user:email'],
    });
  }

  async validate(
    accessToken: string,
    refreshToken: string,
    profile: any,
  ): Promise<any> {
    const { username, emails, photos } = profile;
    
    const user = await this.authService.findOrCreateOAuthUser({
      email: emails[0].value,
      username,
      avatar: photos[0].value,
      provider: 'github',
      providerId: profile.id,
      accessToken,
      refreshToken,
    });

    return user;
  }
}

14.3 JWT-Implementation

JSON Web Tokens (JWT) sind der moderne Standard für stateless Authentication in APIs und Single Page Applications.

14.3.1 JWT Payload Interface

export interface JwtPayload {
  sub: string;        // Subject (User ID)
  email: string;      // User email
  roles: string[];    // User roles
  permissions: string[]; // User permissions
  iat: number;        // Issued at
  exp: number;        // Expiration time
  jti?: string;       // JWT ID (for revocation)
  iss: string;        // Issuer
  aud: string;        // Audience
}

export interface RefreshTokenPayload {
  sub: string;
  tokenId: string;
  iat: number;
  exp: number;
}

14.3.2 Token-Generierung

import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import { UsersService } from '../users/users.service';
import { User } from '../entities/user.entity';
import { JwtPayload, RefreshTokenPayload } from './interfaces/jwt-payload.interface';
import * as bcrypt from 'bcrypt';
import { v4 as uuidv4 } from 'uuid';

@Injectable()
export class AuthService {
  constructor(
    private usersService: UsersService,
    private jwtService: JwtService,
    private configService: ConfigService,
  ) {}

  async validateUser(email: string, password: string): Promise<User | null> {
    const user = await this.usersService.findByEmail(email, true);
    
    if (!user) {
      // Simulate password check to prevent timing attacks
      await bcrypt.compare(password, '$2b$12$dummy.hash.to.prevent.timing.attacks');
      return null;
    }

    const isPasswordValid = await bcrypt.compare(password, user.password);
    
    if (!isPasswordValid) {
      // Log failed login attempt
      await this.logFailedLoginAttempt(user.id, email);
      return null;
    }

    // Reset failed login attempts on successful validation
    await this.resetFailedLoginAttempts(user.id);
    
    const { password: _, ...result } = user;
    return result as User;
  }

  async login(user: User): Promise<{
    accessToken: string;
    refreshToken: string;
    expiresIn: number;
    user: Partial<User>;
  }> {
    const tokenId = uuidv4();
    
    const payload: JwtPayload = {
      sub: user.id,
      email: user.email,
      roles: user.roles || [],
      permissions: await this.getUserPermissions(user),
      iat: Math.floor(Date.now() / 1000),
      exp: Math.floor(Date.now() / 1000) + this.getAccessTokenTTL(),
      jti: tokenId,
      iss: this.configService.get<string>('JWT_ISSUER'),
      aud: this.configService.get<string>('JWT_AUDIENCE'),
    };

    const refreshPayload: RefreshTokenPayload = {
      sub: user.id,
      tokenId,
      iat: Math.floor(Date.now() / 1000),
      exp: Math.floor(Date.now() / 1000) + this.getRefreshTokenTTL(),
    };

    const [accessToken, refreshToken] = await Promise.all([
      this.jwtService.signAsync(payload),
      this.jwtService.signAsync(refreshPayload, {
        secret: this.configService.get<string>('JWT_REFRESH_SECRET'),
        expiresIn: this.configService.get<string>('JWT_REFRESH_EXPIRES_IN', '7d'),
      }),
    ]);

    // Store refresh token
    await this.storeRefreshToken(user.id, tokenId, refreshToken);
    
    // Log successful login
    await this.logSuccessfulLogin(user.id);

    return {
      accessToken,
      refreshToken,
      expiresIn: this.getAccessTokenTTL(),
      user: {
        id: user.id,
        email: user.email,
        firstName: user.firstName,
        lastName: user.lastName,
        roles: user.roles,
      },
    };
  }

  async refreshTokens(refreshToken: string): Promise<{
    accessToken: string;
    refreshToken: string;
    expiresIn: number;
  }> {
    try {
      const payload = await this.jwtService.verifyAsync<RefreshTokenPayload>(
        refreshToken,
        {
          secret: this.configService.get<string>('JWT_REFRESH_SECRET'),
        },
      );

      // Validate stored refresh token
      const storedToken = await this.getStoredRefreshToken(payload.sub, payload.tokenId);
      if (!storedToken || storedToken !== refreshToken) {
        throw new UnauthorizedException('Invalid refresh token');
      }

      const user = await this.usersService.findById(payload.sub);
      if (!user || !user.isActive) {
        throw new UnauthorizedException('User not found or inactive');
      }

      // Generate new tokens
      const newTokenId = uuidv4();
      const newPayload: JwtPayload = {
        sub: user.id,
        email: user.email,
        roles: user.roles || [],
        permissions: await this.getUserPermissions(user),
        iat: Math.floor(Date.now() / 1000),
        exp: Math.floor(Date.now() / 1000) + this.getAccessTokenTTL(),
        jti: newTokenId,
        iss: this.configService.get<string>('JWT_ISSUER'),
        aud: this.configService.get<string>('JWT_AUDIENCE'),
      };

      const newRefreshPayload: RefreshTokenPayload = {
        sub: user.id,
        tokenId: newTokenId,
        iat: Math.floor(Date.now() / 1000),
        exp: Math.floor(Date.now() / 1000) + this.getRefreshTokenTTL(),
      };

      const [newAccessToken, newRefreshToken] = await Promise.all([
        this.jwtService.signAsync(newPayload),
        this.jwtService.signAsync(newRefreshPayload, {
          secret: this.configService.get<string>('JWT_REFRESH_SECRET'),
          expiresIn: this.configService.get<string>('JWT_REFRESH_EXPIRES_IN', '7d'),
        }),
      ]);

      // Replace old refresh token
      await this.replaceRefreshToken(
        payload.sub,
        payload.tokenId,
        newTokenId,
        newRefreshToken,
      );

      return {
        accessToken: newAccessToken,
        refreshToken: newRefreshToken,
        expiresIn: this.getAccessTokenTTL(),
      };
    } catch (error) {
      throw new UnauthorizedException('Invalid refresh token');
    }
  }

  private getAccessTokenTTL(): number {
    return parseInt(this.configService.get<string>('JWT_ACCESS_TTL', '3600')); // 1 hour
  }

  private getRefreshTokenTTL(): number {
    return parseInt(this.configService.get<string>('JWT_REFRESH_TTL', '604800')); // 7 days
  }

  private async getUserPermissions(user: User): Promise<string[]> {
    // Implement based on your role/permission system
    const permissions: string[] = [];
    
    if (user.roles?.includes('admin')) {
      permissions.push('*'); // All permissions
    } else if (user.roles?.includes('user')) {
      permissions.push('read:own', 'write:own');
    }

    return permissions;
  }

  private async storeRefreshToken(
    userId: string,
    tokenId: string,
    token: string,
  ): Promise<void> {
    // Implementation depends on your storage solution (Redis, Database, etc.)
    // Example with Redis:
    // await this.redis.setex(`refresh_token:${userId}:${tokenId}`, this.getRefreshTokenTTL(), token);
  }

  private async getStoredRefreshToken(
    userId: string,
    tokenId: string,
  ): Promise<string | null> {
    // Implementation depends on your storage solution
    // Example with Redis:
    // return await this.redis.get(`refresh_token:${userId}:${tokenId}`);
    return null; // Placeholder
  }

  private async replaceRefreshToken(
    userId: string,
    oldTokenId: string,
    newTokenId: string,
    newToken: string,
  ): Promise<void> {
    // Remove old token and store new one
    // await this.redis.del(`refresh_token:${userId}:${oldTokenId}`);
    // await this.redis.setex(`refresh_token:${userId}:${newTokenId}`, this.getRefreshTokenTTL(), newToken);
  }

  private async logFailedLoginAttempt(userId: string, email: string): Promise<void> {
    // Implement failed login attempt tracking
    console.log(`Failed login attempt for user ${email}`);
  }

  private async resetFailedLoginAttempts(userId: string): Promise<void> {
    // Reset failed login attempt counter
    console.log(`Reset failed login attempts for user ${userId}`);
  }

  private async logSuccessfulLogin(userId: string): Promise<void> {
    // Log successful login
    console.log(`Successful login for user ${userId}`);
  }
}

14.3.3 Token-Validierung

import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import { Reflector } from '@nestjs/core';
import { IS_PUBLIC_KEY } from '../decorators/public.decorator';

@Injectable()
export class JwtAuthGuard implements CanActivate {
  constructor(
    private jwtService: JwtService,
    private configService: ConfigService,
    private reflector: Reflector,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    // Check if route is marked as public
    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);

    if (isPublic) {
      return true;
    }

    const request = context.switchToHttp().getRequest();
    const token = this.extractTokenFromHeader(request);

    if (!token) {
      throw new UnauthorizedException('No token provided');
    }

    try {
      const payload = await this.jwtService.verifyAsync(token, {
        secret: this.configService.get<string>('JWT_SECRET'),
        issuer: this.configService.get<string>('JWT_ISSUER'),
        audience: this.configService.get<string>('JWT_AUDIENCE'),
      });

      // Additional validation
      await this.validateTokenPayload(payload);
      
      request.user = payload;
      return true;
    } catch (error) {
      if (error.name === 'TokenExpiredError') {
        throw new UnauthorizedException('Token has expired');
      } else if (error.name === 'JsonWebTokenError') {
        throw new UnauthorizedException('Invalid token');
      } else {
        throw new UnauthorizedException('Token validation failed');
      }
    }
  }

  private extractTokenFromHeader(request: any): string | undefined {
    const [type, token] = request.headers.authorization?.split(' ') ?? [];
    return type === 'Bearer' ? token : undefined;
  }

  private async validateTokenPayload(payload: any): Promise<void> {
    // Additional custom validation
    if (!payload.sub || !payload.email) {
      throw new UnauthorizedException('Invalid token payload');
    }

    // Check if token is revoked (if you implement token revocation)
    if (payload.jti && await this.isTokenRevoked(payload.jti)) {
      throw new UnauthorizedException('Token has been revoked');
    }
  }

  private async isTokenRevoked(jti: string): Promise<boolean> {
    // Implement token revocation check
    // Example with Redis:
    // return await this.redis.exists(`revoked_token:${jti}`);
    return false; // Placeholder
  }
}

14.3.4 Refresh Token Pattern

import { Controller, Post, Body, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';
import { Public } from './decorators/public.decorator';

@Controller('auth')
export class AuthController {
  constructor(private authService: AuthService) {}

  @Public()
  @Post('login')
  async login(@Body() loginDto: { email: string; password: string }) {
    const user = await this.authService.validateUser(loginDto.email, loginDto.password);
    
    if (!user) {
      throw new UnauthorizedException('Invalid credentials');
    }

    return this.authService.login(user);
  }

  @Public()
  @Post('refresh')
  async refresh(@Body() refreshDto: { refreshToken: string }) {
    if (!refreshDto.refreshToken) {
      throw new UnauthorizedException('Refresh token is required');
    }

    return this.authService.refreshTokens(refreshDto.refreshToken);
  }

  @Post('logout')
  async logout(@Body() logoutDto: { refreshToken: string }) {
    if (logoutDto.refreshToken) {
      await this.authService.revokeRefreshToken(logoutDto.refreshToken);
    }
    
    return { message: 'Logged out successfully' };
  }

  @Post('logout-all')
  async logoutAll(@Req() req: any) {
    const userId = req.user.sub;
    await this.authService.revokeAllRefreshTokens(userId);
    
    return { message: 'Logged out from all devices' };
  }
}

14.4 Role-based Access Control (RBAC)

RBAC ist ein bewährtes Modell für die Zugriffskontrolle in Unternehmensanwendungen.

14.4.1 Role und Permission Entities

import { Entity, Column, PrimaryGeneratedColumn, ManyToMany, JoinTable } from 'typeorm';

@Entity('roles')
export class Role {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ unique: true })
  name: string;

  @Column({ nullable: true })
  description: string;

  @Column({ default: true })
  isActive: boolean;

  @ManyToMany(() => Permission, permission => permission.roles)
  @JoinTable({
    name: 'role_permissions',
    joinColumn: { name: 'roleId', referencedColumnName: 'id' },
    inverseJoinColumn: { name: 'permissionId', referencedColumnName: 'id' },
  })
  permissions: Permission[];
}

@Entity('permissions')
export class Permission {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ unique: true })
  name: string;

  @Column({ nullable: true })
  description: string;

  @Column()
  resource: string; // e.g., 'users', 'posts', 'orders'

  @Column()
  action: string; // e.g., 'create', 'read', 'update', 'delete'

  @ManyToMany(() => Role, role => role.permissions)
  roles: Role[];
}

// Updated User Entity
@Entity('users')
export class User extends BaseEntity {
  // ... other properties

  @ManyToMany(() => Role, role => role.users)
  @JoinTable({
    name: 'user_roles',
    joinColumn: { name: 'userId', referencedColumnName: 'id' },
    inverseJoinColumn: { name: 'roleId', referencedColumnName: 'id' },
  })
  roles: Role[];

  // Helper method to get all permissions
  getAllPermissions(): string[] {
    const permissions = new Set<string>();
    
    this.roles?.forEach(role => {
      role.permissions?.forEach(permission => {
        permissions.add(`${permission.action}:${permission.resource}`);
      });
    });

    return Array.from(permissions);
  }

  // Helper method to check permission
  hasPermission(action: string, resource: string): boolean {
    return this.getAllPermissions().includes(`${action}:${resource}`) ||
           this.getAllPermissions().includes('*'); // Super admin
  }

  // Helper method to check role
  hasRole(roleName: string): boolean {
    return this.roles?.some(role => role.name === roleName) || false;
  }
}

14.4.2 RBAC Service

@Injectable()
export class RbacService {
  constructor(
    @InjectRepository(Role)
    private roleRepository: Repository<Role>,
    @InjectRepository(Permission)
    private permissionRepository: Repository<Permission>,
    @InjectRepository(User)
    private userRepository: Repository<User>,
  ) {}

  async createRole(name: string, description?: string): Promise<Role> {
    const role = this.roleRepository.create({ name, description });
    return this.roleRepository.save(role);
  }

  async createPermission(
    name: string,
    resource: string,
    action: string,
    description?: string,
  ): Promise<Permission> {
    const permission = this.permissionRepository.create({
      name,
      resource,
      action,
      description,
    });
    return this.permissionRepository.save(permission);
  }

  async assignRoleToUser(userId: string, roleId: string): Promise<void> {
    const user = await this.userRepository.findOne({
      where: { id: userId },
      relations: ['roles'],
    });

    const role = await this.roleRepository.findOne({
      where: { id: roleId },
    });

    if (!user || !role) {
      throw new NotFoundException('User or role not found');
    }

    if (!user.roles) {
      user.roles = [];
    }

    if (!user.roles.find(r => r.id === roleId)) {
      user.roles.push(role);
      await this.userRepository.save(user);
    }
  }

  async removeRoleFromUser(userId: string, roleId: string): Promise<void> {
    const user = await this.userRepository.findOne({
      where: { id: userId },
      relations: ['roles'],
    });

    if (!user) {
      throw new NotFoundException('User not found');
    }

    user.roles = user.roles?.filter(role => role.id !== roleId) || [];
    await this.userRepository.save(user);
  }

  async assignPermissionToRole(roleId: string, permissionId: string): Promise<void> {
    const role = await this.roleRepository.findOne({
      where: { id: roleId },
      relations: ['permissions'],
    });

    const permission = await this.permissionRepository.findOne({
      where: { id: permissionId },
    });

    if (!role || !permission) {
      throw new NotFoundException('Role or permission not found');
    }

    if (!role.permissions) {
      role.permissions = [];
    }

    if (!role.permissions.find(p => p.id === permissionId)) {
      role.permissions.push(permission);
      await this.roleRepository.save(role);
    }
  }

  async getUserPermissions(userId: string): Promise<string[]> {
    const user = await this.userRepository.findOne({
      where: { id: userId },
      relations: ['roles', 'roles.permissions'],
    });

    if (!user) {
      return [];
    }

    return user.getAllPermissions();
  }

  async hasPermission(
    userId: string,
    action: string,
    resource: string,
  ): Promise<boolean> {
    const permissions = await this.getUserPermissions(userId);
    return permissions.includes(`${action}:${resource}`) || 
           permissions.includes('*');
  }

  async hasRole(userId: string, roleName: string): Promise<boolean> {
    const user = await this.userRepository.findOne({
      where: { id: userId },
      relations: ['roles'],
    });

    return user?.hasRole(roleName) || false;
  }

  // Seed default roles and permissions
  async seedDefaultRolesAndPermissions(): Promise<void> {
    // Create default permissions
    const permissions = [
      { name: 'Create Users', resource: 'users', action: 'create' },
      { name: 'Read Users', resource: 'users', action: 'read' },
      { name: 'Update Users', resource: 'users', action: 'update' },
      { name: 'Delete Users', resource: 'users', action: 'delete' },
      { name: 'Create Posts', resource: 'posts', action: 'create' },
      { name: 'Read Posts', resource: 'posts', action: 'read' },
      { name: 'Update Posts', resource: 'posts', action: 'update' },
      { name: 'Delete Posts', resource: 'posts', action: 'delete' },
      { name: 'Admin Access', resource: '*', action: '*' },
    ];

    const createdPermissions = await Promise.all(
      permissions.map(async (perm) => {
        const existing = await this.permissionRepository.findOne({
          where: { name: perm.name },
        });
        
        if (!existing) {
          return this.createPermission(perm.name, perm.resource, perm.action);
        }
        return existing;
      }),
    );

    // Create default roles
    const adminRole = await this.roleRepository.findOne({ where: { name: 'admin' } }) ||
                     await this.createRole('admin', 'Administrator role');
    
    const userRole = await this.roleRepository.findOne({ where: { name: 'user' } }) ||
                     await this.createRole('user', 'Regular user role');

    // Assign permissions to roles
    adminRole.permissions = createdPermissions;
    await this.roleRepository.save(adminRole);

    userRole.permissions = createdPermissions.filter(p => 
      p.resource === 'posts' && ['create', 'read', 'update'].includes(p.action)
    );
    await this.roleRepository.save(userRole);
  }
}

14.5 Guards für Authorization

Guards in NestJS sind der primäre Mechanismus für die Implementierung von Authorization-Logic.

14.5.1 Role Guard

import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from '../decorators/roles.decorator';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);

    if (!requiredRoles) {
      return true;
    }

    const request = context.switchToHttp().getRequest();
    const user = request.user;

    if (!user) {
      throw new ForbiddenException('User not found in request');
    }

    const hasRole = requiredRoles.some(role => user.roles?.includes(role));

    if (!hasRole) {
      throw new ForbiddenException(`Insufficient permissions. Required roles: ${requiredRoles.join(', ')}`);
    }

    return true;
  }
}

14.5.2 Permission Guard

import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { RbacService } from '../rbac/rbac.service';
import { PERMISSIONS_KEY } from '../decorators/permissions.decorator';

@Injectable()
export class PermissionsGuard implements CanActivate {
  constructor(
    private reflector: Reflector,
    private rbacService: RbacService,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const requiredPermissions = this.reflector.getAllAndOverride<{
      action: string;
      resource: string;
    }[]>(PERMISSIONS_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);

    if (!requiredPermissions) {
      return true;
    }

    const request = context.switchToHttp().getRequest();
    const user = request.user;

    if (!user) {
      throw new ForbiddenException('User not found in request');
    }

    // Check all required permissions
    for (const permission of requiredPermissions) {
      const hasPermission = await this.rbacService.hasPermission(
        user.sub,
        permission.action,
        permission.resource,
      );

      if (!hasPermission) {
        throw new ForbiddenException(
          `Insufficient permissions. Required: ${permission.action}:${permission.resource}`,
        );
      }
    }

    return true;
  }
}

14.5.3 Resource Owner Guard

import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { RESOURCE_OWNER_KEY } from '../decorators/resource-owner.decorator';

@Injectable()
export class ResourceOwnerGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const resourceOwnerConfig = this.reflector.get<{
      userIdParam: string;
      allowRoles?: string[];
    }>(RESOURCE_OWNER_KEY, context.getHandler());

    if (!resourceOwnerConfig) {
      return true;
    }

    const request = context.switchToHttp().getRequest();
    const user = request.user;
    const resourceUserId = request.params[resourceOwnerConfig.userIdParam];

    if (!user) {
      throw new ForbiddenException('User not found in request');
    }

    // Allow if user is the resource owner
    if (user.sub === resourceUserId) {
      return true;
    }

    // Allow if user has one of the allowed roles
    if (resourceOwnerConfig.allowRoles) {
      const hasAllowedRole = resourceOwnerConfig.allowRoles.some(role =>
        user.roles?.includes(role),
      );

      if (hasAllowedRole) {
        return true;
      }
    }

    throw new ForbiddenException('Access denied. You can only access your own resources.');
  }
}

14.5.4 Decorators für Authorization

// roles.decorator.ts
import { SetMetadata } from '@nestjs/common';

export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);

// permissions.decorator.ts
import { SetMetadata } from '@nestjs/common';

export const PERMISSIONS_KEY = 'permissions';
export const RequirePermissions = (...permissions: Array<{ action: string; resource: string }>) =>
  SetMetadata(PERMISSIONS_KEY, permissions);

// resource-owner.decorator.ts
import { SetMetadata } from '@nestjs/common';

export const RESOURCE_OWNER_KEY = 'resource_owner';
export const ResourceOwner = (userIdParam: string, allowRoles?: string[]) =>
  SetMetadata(RESOURCE_OWNER_KEY, { userIdParam, allowRoles });

// public.decorator.ts
import { SetMetadata } from '@nestjs/common';

export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

14.5.5 Usage in Controllers

@Controller('users')
@UseGuards(JwtAuthGuard, RolesGuard, PermissionsGuard)
export class UsersController {
  constructor(private usersService: UsersService) {}

  @Get()
  @RequirePermissions({ action: 'read', resource: 'users' })
  async findAll(): Promise<User[]> {
    return this.usersService.findAll();
  }

  @Get(':id')
  @ResourceOwner('id', ['admin'])
  async findOne(@Param('id') id: string): Promise<User> {
    return this.usersService.findOne(id);
  }

  @Post()
  @Roles('admin')
  @RequirePermissions({ action: 'create', resource: 'users' })
  async create(@Body() createUserDto: CreateUserDto): Promise<User> {
    return this.usersService.create(createUserDto);
  }

  @Put(':id')
  @ResourceOwner('id', ['admin'])
  @RequirePermissions({ action: 'update', resource: 'users' })
  async update(
    @Param('id') id: string,
    @Body() updateUserDto: UpdateUserDto,
  ): Promise<User> {
    return this.usersService.update(id, updateUserDto);
  }

  @Delete(':id')
  @Roles('admin')
  @RequirePermissions({ action: 'delete', resource: 'users' })
  async remove(@Param('id') id: string): Promise<void> {
    return this.usersService.remove(id);
  }
}

14.6 Session Management

Obwohl JWT stateless ist, gibt es Szenarien, in denen Session Management erforderlich ist.

14.6.1 Session-basierte Authentication

import { Injectable } from '@nestjs/common';
import { PassportSerializer } from '@nestjs/passport';
import { UsersService } from '../users/users.service';
import { User } from '../entities/user.entity';

@Injectable()
export class SessionSerializer extends PassportSerializer {
  constructor(private usersService: UsersService) {
    super();
  }

  serializeUser(user: User, done: (err: Error, user: any) => void): any {
    done(null, { id: user.id, email: user.email });
  }

  async deserializeUser(
    payload: { id: string; email: string },
    done: (err: Error, payload: string) => void,
  ): Promise<any> {
    const user = await this.usersService.findById(payload.id);
    done(null, user);
  }
}

14.6.2 Session Store mit Redis

import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as session from 'express-session';
import * as connectRedis from 'connect-redis';
import { Redis } from 'ioredis';

@Module({
  providers: [
    {
      provide: 'SESSION_OPTIONS',
      useFactory: (configService: ConfigService) => {
        const RedisStore = connectRedis(session);
        const redisClient = new Redis({
          host: configService.get('REDIS_HOST'),
          port: configService.get('REDIS_PORT'),
          password: configService.get('REDIS_PASSWORD'),
        });

        return {
          store: new RedisStore({ client: redisClient }),
          secret: configService.get('SESSION_SECRET'),
          resave: false,
          saveUninitialized: false,
          cookie: {
            maxAge: 1000 * 60 * 60 * 24, // 1 day
            httpOnly: true,
            secure: configService.get('NODE_ENV') === 'production',
            sameSite: 'strict',
          },
        };
      },
      inject: [ConfigService],
    },
  ],
  exports: ['SESSION_OPTIONS'],
})
export class SessionModule {}

14.7 Multi-Factor Authentication (MFA)

MFA fügt eine zusätzliche Sicherheitsebene hinzu und ist für moderne Anwendungen unerlässlich.

14.7.1 TOTP (Time-based One-Time Password) Implementation

import { Injectable } from '@nestjs/common';
import * as speakeasy from 'speakeasy';
import * as QRCode from 'qrcode';
import { UsersService } from '../users/users.service';

@Injectable()
export class MfaService {
  constructor(private usersService: UsersService) {}

  async generateSecret(userId: string): Promise<{
    secret: string;
    qrCodeUrl: string;
    manualEntryKey: string;
  }> {
    const user = await this.usersService.findById(userId);
    
    const secret = speakeasy.generateSecret({
      name: `${user.email}`,
      issuer: 'YourApp',
      length: 32,
    });

    // Generate QR code
    const qrCodeUrl = await QRCode.toDataURL(secret.otpauth_url);

    // Store the secret (temporarily) - user needs to verify before it's saved permanently
    await this.usersService.setTempMfaSecret(userId, secret.base32);

    return {
      secret: secret.base32,
      qrCodeUrl,
      manualEntryKey: secret.base32,
    };
  }

  async verifyAndEnableMfa(
    userId: string,
    token: string,
  ): Promise<{ success: boolean; backupCodes: string[] }> {
    const user = await this.usersService.findById(userId);
    
    if (!user.tempMfaSecret) {
      throw new BadRequestException('No MFA setup in progress');
    }

    const verified = speakeasy.totp.verify({
      secret: user.tempMfaSecret,
      encoding: 'base32',
      token,
      window: 2, // Allow some time drift
    });

    if (!verified) {
      throw new BadRequestException('Invalid verification code');
    }

    // Generate backup codes
    const backupCodes = this.generateBackupCodes();

    // Save MFA settings
    await this.usersService.enableMfa(userId, user.tempMfaSecret, backupCodes);

    return {
      success: true,
      backupCodes,
    };
  }

  async verifyMfaToken(userId: string, token: string): Promise<boolean> {
    const user = await this.usersService.findById(userId);
    
    if (!user.mfaSecret) {
      return false;
    }

    // Check if it's a backup code
    if (await this.verifyBackupCode(userId, token)) {
      return true;
    }

    // Verify TOTP token
    return speakeasy.totp.verify({
      secret: user.mfaSecret,
      encoding: 'base32',
      token,
      window: 2,
    });
  }

  async disableMfa(userId: string, token: string): Promise<void> {
    if (!await this.verifyMfaToken(userId, token)) {
      throw new BadRequestException('Invalid verification code');
    }

    await this.usersService.disableMfa(userId);
  }

  private generateBackupCodes(): string[] {
    const codes: string[] = [];
    
    for (let i = 0; i < 10; i++) {
      // Generate 8-character backup codes
      const code = Math.random().toString(36).substring(2, 10).toUpperCase();
      codes.push(code);
    }

    return codes;
  }

  private async verifyBackupCode(userId: string, code: string): Promise<boolean> {
    const user = await this.usersService.findById(userId);
    
    if (!user.mfaBackupCodes?.includes(code)) {
      return false;
    }

    // Remove used backup code
    await this.usersService.removeUsedBackupCode(userId, code);
    
    return true;
  }
}

14.7.2 MFA Guards

import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { SKIP_MFA_KEY } from '../decorators/skip-mfa.decorator';

@Injectable()
export class MfaGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    // Check if MFA should be skipped for this route
    const skipMfa = this.reflector.getAllAndOverride<boolean>(SKIP_MFA_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);

    if (skipMfa) {
      return true;
    }

    const request = context.switchToHttp().getRequest();
    const user = request.user;

    if (!user) {
      throw new UnauthorizedException('User not authenticated');
    }

    // If user has MFA enabled, check if they've completed MFA for this session
    if (user.mfaEnabled && !user.mfaVerified) {
      throw new UnauthorizedException('MFA verification required');
    }

    return true;
  }
}

// Decorator to skip MFA for certain routes
export const SKIP_MFA_KEY = 'skipMfa';
export const SkipMfa = () => SetMetadata(SKIP_MFA_KEY, true);

14.7.3 MFA Controller

@Controller('auth/mfa')
@UseGuards(JwtAuthGuard)
export class MfaController {
  constructor(
    private mfaService: MfaService,
    private authService: AuthService,
  ) {}

  @Post('setup')
  async setupMfa(@Req() req: any) {
    const userId = req.user.sub;
    return this.mfaService.generateSecret(userId);
  }

  @Post('verify-setup')
  async verifySetup(
    @Req() req: any,
    @Body() verifyDto: { token: string },
  ) {
    const userId = req.user.sub;
    return this.mfaService.verifyAndEnableMfa(userId, verifyDto.token);
  }

  @Post('verify')
  @SkipMfa()
  async verifyMfa(
    @Req() req: any,
    @Body() verifyDto: { token: string },
  ) {
    const userId = req.user.sub;
    const isValid = await this.mfaService.verifyMfaToken(userId, verifyDto.token);
    
    if (!isValid) {
      throw new UnauthorizedException('Invalid MFA token');
    }

    // Mark MFA as verified for this session
    const updatedToken = await this.authService.markMfaAsVerified(userId);
    
    return {
      success: true,
      accessToken: updatedToken,
    };
  }

  @Post('disable')
  async disableMfa(
    @Req() req: any,
    @Body() disableDto: { token: string },
  ) {
    const userId = req.user.sub;
    await this.mfaService.disableMfa(userId, disableDto.token);
    
    return { success: true, message: 'MFA disabled successfully' };
  }

  @Get('backup-codes')
  async generateBackupCodes(@Req() req: any) {
    const userId = req.user.sub;
    // Regenerate backup codes (invalidate old ones)
    return this.mfaService.regenerateBackupCodes(userId);
  }
}

14.8 Sicherheitshinweise für moderne NestJS-Anwendungen

14.8.1 Environment-Variables und Secrets

// config/security.config.ts
export const securityConfig = () => ({
  jwt: {
    secret: process.env.JWT_SECRET || (() => {
      throw new Error('JWT_SECRET is required');
    })(),
    expiresIn: process.env.JWT_EXPIRES_IN || '1h',
    refreshSecret: process.env.JWT_REFRESH_SECRET || (() => {
      throw new Error('JWT_REFRESH_SECRET is required');
    })(),
    refreshExpiresIn: process.env.JWT_REFRESH_EXPIRES_IN || '7d',
  },
  bcrypt: {
    saltRounds: parseInt(process.env.BCRYPT_SALT_ROUNDS || '12'),
  },
  rateLimit: {
    windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS || '900000'), // 15 minutes
    max: parseInt(process.env.RATE_LIMIT_MAX || '100'), // limit each IP to 100 requests per windowMs
  },
  session: {
    secret: process.env.SESSION_SECRET || (() => {
      throw new Error('SESSION_SECRET is required');
    })(),
    maxAge: parseInt(process.env.SESSION_MAX_AGE || '86400000'), // 24 hours
  },
  mfa: {
    issuer: process.env.MFA_ISSUER || 'YourApp',
    windowSize: parseInt(process.env.MFA_WINDOW_SIZE || '2'),
  },
});

14.8.2 Password Security Best Practices

@Injectable()
export class PasswordService {
  private readonly saltRounds = 12;
  
  // Password complexity requirements
  private readonly passwordRequirements = {
    minLength: 8,
    maxLength: 128,
    requireUppercase: true,
    requireLowercase: true,
    requireNumbers: true,
    requireSpecialChars: true,
    prohibitCommonPasswords: true,
  };

  async hashPassword(password: string): Promise<string> {
    // Validate password strength
    this.validatePasswordStrength(password);
    
    return bcrypt.hash(password, this.saltRounds);
  }

  async verifyPassword(password: string, hash: string): Promise<boolean> {
    return bcrypt.compare(password, hash);
  }

  validatePasswordStrength(password: string): void {
    const errors: string[] = [];

    if (password.length < this.passwordRequirements.minLength) {
      errors.push(`Password must be at least ${this.passwordRequirements.minLength} characters long`);
    }

    if (password.length > this.passwordRequirements.maxLength) {
      errors.push(`Password must be no more than ${this.passwordRequirements.maxLength} characters long`);
    }

    if (this.passwordRequirements.requireUppercase && !/[A-Z]/.test(password)) {
      errors.push('Password must contain at least one uppercase letter');
    }

    if (this.passwordRequirements.requireLowercase && !/[a-z]/.test(password)) {
      errors.push('Password must contain at least one lowercase letter');
    }

    if (this.passwordRequirements.requireNumbers && !/\d/.test(password)) {
      errors.push('Password must contain at least one number');
    }

    if (this.passwordRequirements.requireSpecialChars && !/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
      errors.push('Password must contain at least one special character');
    }

    if (this.passwordRequirements.prohibitCommonPasswords && this.isCommonPassword(password)) {
      errors.push('Password is too common. Please choose a more unique password');
    }

    if (errors.length > 0) {
      throw new BadRequestException(`Password validation failed: ${errors.join(', ')}`);
    }
  }

  private isCommonPassword(password: string): boolean {
    const commonPasswords = [
      'password', '123456', '123456789', 'qwerty', 'abc123',
      'password123', 'admin', 'letmein', 'welcome', 'monkey',
    ];
    
    return commonPasswords.includes(password.toLowerCase());
  }

  generateSecurePassword(length: number = 16): string {
    const charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+-=[]{}|;:,.<>?';
    let password = '';
    
    for (let i = 0; i < length; i++) {
      password += charset.charAt(Math.floor(Math.random() * charset.length));
    }
    
    return password;
  }
}

14.8.3 Account Security Features

@Injectable()
export class AccountSecurityService {
  constructor(
    private usersService: UsersService,
    private configService: ConfigService,
  ) {}

  async lockAccountAfterFailedAttempts(userId: string): Promise<void> {
    const user = await this.usersService.findById(userId);
    const maxAttempts = this.configService.get<number>('MAX_LOGIN_ATTEMPTS', 5);
    const lockoutDuration = this.configService.get<number>('LOCKOUT_DURATION_MS', 30 * 60 * 1000); // 30 minutes

    user.failedLoginAttempts = (user.failedLoginAttempts || 0) + 1;

    if (user.failedLoginAttempts >= maxAttempts) {
      user.lockedUntil = new Date(Date.now() + lockoutDuration);
      
      // Send security alert email
      await this.sendSecurityAlert(user.email, 'account_locked');
    }

    await this.usersService.save(user);
  }

  async resetFailedLoginAttempts(userId: string): Promise<void> {
    const user = await this.usersService.findById(userId);
    user.failedLoginAttempts = 0;
    user.lockedUntil = null;
    await this.usersService.save(user);
  }

  async detectSuspiciousActivity(userId: string, ipAddress: string, userAgent: string): Promise<void> {
    const user = await this.usersService.findById(userId);
    const lastLogin = user.lastLoginAt;
    const lastIp = user.lastLoginIp;

    // Check for unusual login patterns
    if (lastLogin && lastIp && lastIp !== ipAddress) {
      const timeDiff = Date.now() - lastLogin.getTime();
      const minTimeForLocationChange = 30 * 60 * 1000; // 30 minutes

      if (timeDiff < minTimeForLocationChange) {
        // Potential suspicious activity
        await this.sendSecurityAlert(user.email, 'suspicious_login', {
          ipAddress,
          userAgent,
          previousIp: lastIp,
        });
      }
    }

    // Update login information
    user.lastLoginAt = new Date();
    user.lastLoginIp = ipAddress;
    user.lastUserAgent = userAgent;
    await this.usersService.save(user);
  }

  async requirePasswordChange(userId: string, reason: string): Promise<void> {
    const user = await this.usersService.findById(userId);
    user.mustChangePassword = true;
    user.passwordChangeReason = reason;
    await this.usersService.save(user);

    await this.sendSecurityAlert(user.email, 'password_change_required', { reason });
  }

  async logSecurityEvent(
    userId: string,
    eventType: string,
    details: Record<string, any>,
  ): Promise<void> {
    // Log to security audit system
    console.log(`Security Event: ${eventType}`, {
      userId,
      timestamp: new Date().toISOString(),
      details,
    });

    // You might want to store this in a dedicated security events table
  }

  private async sendSecurityAlert(
    email: string,
    alertType: string,
    data?: Record<string, any>,
  ): Promise<void> {
    // Implement email service integration
    console.log(`Security Alert: ${alertType} sent to ${email}`, data);
  }
}

14.8.4 Security Headers und CSRF Protection

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as helmet from 'helmet';
import * as csurf from 'csurf';
import * as rateLimit from 'express-rate-limit';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // Security headers
  app.use(helmet({
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"],
        styleSrc: ["'self'", "'unsafe-inline'"],
        scriptSrc: ["'self'"],
        imgSrc: ["'self'", "data:", "https:"],
      },
    },
    hsts: {
      maxAge: 31536000,
      includeSubDomains: true,
      preload: true,
    },
  }));

  // Rate limiting
  app.use(rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 100, // limit each IP to 100 requests per windowMs
    message: 'Too many requests from this IP, please try again later.',
    standardHeaders: true,
    legacyHeaders: false,
  }));

  // CSRF protection (for session-based auth)
  if (process.env.ENABLE_CSRF === 'true') {
    app.use(csurf({
      cookie: {
        httpOnly: true,
        secure: process.env.NODE_ENV === 'production',
        sameSite: 'strict',
      },
    }));
  }

  // CORS configuration
  app.enableCors({
    origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'],
    credentials: true,
    methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
    allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
  });

  await app.listen(3000);
}
bootstrap();

Die Implementierung einer robusten Authentication und Authorization ist fundamental für die Sicherheit moderner NestJS-Anwendungen. Durch die Kombination von JWT, RBAC, MFA und zusätzlichen Sicherheitsmaßnahmen können Sie ein hochsicheres System entwickeln, das den aktuellen Best Practices entspricht.