Aws Cognito, Passport 및 NestJs를 사용한 인증(파트 III)

엔드포인트 보호



인증을 위해 JWT 전략을 사용할 것입니다. 보호된 엔드포인트 리소스에 액세스하려면 Cognito에서 생성한 베어러 토큰을 사용해야 합니다.

먼저 PassportModule를 가져오고 JWT 전략을 기본값으로 지정해 보겠습니다.

auth.module.ts




...
import { PassportModule } from '@nestjs/passport';

@Module({
 imports: [PassportModule.register({ defaultStrategy: 'jwt' })],
    ...
})
export class AuthModule {}



이제 jwt.strategy.ts와 같은 디렉토리에 auth.controller.ts라는 파일을 만듭니다.

이 클래스는 PassportStrategy에서 확장되며 선택한 전략을 통과합니다. 또한 validate() 콜백을 통해 요청이 유효한지 확인합니다. 우리는 또한 AWS_COGNITO_AUTHORITY이어야 하는 새로운 환경 변수https://cognito-idp.YOUR_POOL_REGION.amazonaws.com/AWS_COGNITO_USER_POOL_ID를 사용합니다. 내 풀 지역은 North Virginia이므로 us-east-1도 마찬가지입니다.auth.module.ts 공급자이므로 @Injectable 공급자에 나열하는 것을 잊지 마십시오.

jwt.전략.ts





import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { passportJwtSecret } from 'jwks-rsa';
import { ExtractJwt, Strategy } from 'passport-jwt';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      _audience: process.env.AWS_COGNITO_COGNITO_CLIENT_ID,
      issuer: process.env.AWS_COGNITO_AUTHORITY,
      algorithms: ['RS256'],
      secretOrKeyProvider: passportJwtSecret({
        cache: true,
        rateLimit: true,
        jwksRequestsPerMinute: 5,
        jwksUri: process.env.AWS_COGNITO_AUTHORITY + '/.well-known/jwks.json',
      }),
    });
  }

  async validate(payload: any) {
    return { idUser: payload.sub, email: payload.email };
  }
}



마지막으로 네스트 가드를 사용하여 포켓몬의 목록 끝점을 보호합니다. pokemon.controller.ts로 가서 @UseGuards() 데코레이터 전달AuthGuard('jwt')을 매개변수로 추가해 보겠습니다.
이 같은:

포켓몬.컨트롤러.ts




import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
...

@Controller('api/v1/pokemons')
export class PokemonController {
  constructor(private readonly pokemonService: PokemonService) {}

  @UseGuards(AuthGuard('jwt'))
  @Get()
  listAllPokemons(): Array<Pokemon> {
    return this.pokemonService.listAllPokemons();
  }
}




이제 로그인할 때 생성된 베어러 토큰을 전달하지 않으면 엔드포인트에서 401 Unauthorized를 받게 됩니다.





이제 엔드포인트가 원치 않는 액세스로부터 보호됩니다.

비밀번호 변경



암호를 변경하려면 changeUserPassword 에서 aws-cognito.service.ts라는 다른 방법을 수행해야 합니다. 이 메서드는 현재 및 새 암호를 얻기 위해 다른 DTO를 받습니다. 이 방법에서 Cognito는 나중에 changePassword 방법을 사용하여 암호를 변경하기 위해 먼저 사용자를 인증해야 합니다.

인증-변경-비밀번호-user.dto.ts




import { IsEmail, Matches } from 'class-validator';

export class AuthChangePasswordUserDto {
  @IsEmail()
  email: string;

  /* Minimum eight characters, at least one uppercase letter, one lowercase letter, one number, and one special character */

  @Matches(
    /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$&+,:;=?@#|'<>.^*()%!-])[A-Za-z\d@$&+,:;=?@#|'<>.^*()%!-]{8,}$/,
    { message: 'invalid password' },
  )
  currentPassword: string;

  @Matches(
    /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$&+,:;=?@#|'<>.^*()%!-])[A-Za-z\d@$&+,:;=?@#|'<>.^*()%!-]{8,}$/,
    { message: 'invalid password' },
  )
  newPassword: string;
}




aws-cognito.service.ts




...

import { AuthChangePasswordUserDto } from './dtos/auth-change-password-user.dto';

...

async changeUserPassword(
    authChangePasswordUserDto: AuthChangePasswordUserDto,
  ) {
    const { email, currentPassword, newPassword } = authChangePasswordUserDto;

    const userData = {
      Username: email,
      Pool: this.userPool,
    };

    const authenticationDetails = new AuthenticationDetails({
      Username: email,
      Password: currentPassword,
    });

    const userCognito = new CognitoUser(userData);

    return new Promise((resolve, reject) => {
      userCognito.authenticateUser(authenticationDetails, {
        onSuccess: () => {
          userCognito.changePassword(
            currentPassword,
            newPassword,
            (err, result) => {
              if (err) {
                reject(err);
                return;
              }
              resolve(result);
            },
          );
        },
        onFailure: (err) => {
          reject(err);
        },
      });
    });
  }

  ...


auth.controller에서 AuthChangePasswordUserDto를 통과하고 changeUserPassword에서 aws.cognito.service.ts를 호출하는 새 경로만 수행하면 됩니다.

auth.controller.ts




...

@Post('/change-password')
  @UsePipes(ValidationPipe)
  async changePassword(
    @Body() authChangePasswordUserDto: AuthChangePasswordUserDto,
  ) {
    await this.awsCognitoService.changeUserPassword(authChangePasswordUserDto);
  }

 ...



테스트 후 모든 것이 올바르게 작동하는 것 같습니다.




잊어버린 비밀번호 재설정



잊어버린 암호를 재설정하려면 두 개의 새로운 엔드포인트가 필요합니다. 하나는 고유한 코드를 요청하고 다른 하나는 암호를 전환하기 위한 것입니다.
두 개의 새로운 DTO를 정의하는 것으로 시작합니다.

auth-forgot-password-user.dto.ts




...

import { IsEmail } from 'class-validator';

export class AuthForgotPasswordUserDto {
  @IsEmail()
  email: string;
}

 ...



auth-confirm-password-user.dto.ts




...

import { IsEmail, IsString, Matches } from 'class-validator';

export class AuthConfirmPasswordUserDto {
  @IsEmail()
  email: string;

  @IsString()
  confirmationCode: string;

  @Matches(
    /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$&+,:;=?@#|'<>.^*()%!-])[A-Za-z\d@$&+,:;=?@#|'<>.^*()%!-]{8,}$/,
    { message: 'invalid password' },
  )
  newPassword: string;
}



또한 aws.service.ts에서 Cognito에 새 재설정 코드를 요청하고 암호를 변경하기 위해 두 가지 새로운 메서드를 만들어야 합니다.

aws-cognito.service.ts




...

import { AuthConfirmPasswordUserDto } from './dtos/auth-confirm-password-user.dto';
import { AuthForgotPasswordUserDto } from './dtos/auth-forgot-password-user.dto';

...

  async forgotUserPassword(
    authForgotPasswordUserDto: AuthForgotPasswordUserDto,
  ) {
    const { email } = authForgotPasswordUserDto;

    const userData = {
      Username: email,
      Pool: this.userPool,
    };

    const userCognito = new CognitoUser(userData);

    return new Promise((resolve, reject) => {
      userCognito.forgotPassword({
        onSuccess: (result) => {
          resolve(result);
        },
        onFailure: (err) => {
          reject(err);
        },
      });
    });
  }

  async confirmUserPassword(
    authConfirmPasswordUserDto: AuthConfirmPasswordUserDto,
  ) {
    const { email, confirmationCode, newPassword } = authConfirmPasswordUserDto;

    const userData = {
      Username: email,
      Pool: this.userPool,
    };

    const userCognito = new CognitoUser(userData);

    return new Promise((resolve, reject) => {
      userCognito.confirmPassword(confirmationCode, newPassword, {
        onSuccess: () => {
          resolve({ status: 'success' });
        },
        onFailure: (err) => {
          reject(err);
        },
      });
    });
  }

  ...



각각의 서비스 메서드를 호출하여 API에 대한 두 개의 새 경로를 추가하는 컨트롤러에서 거의 동일한 상황입니다.

auth.controller.ts




...

  @Post('/forgot-password')
  @UsePipes(ValidationPipe)
  async forgotPassword(
    @Body() authForgotPasswordUserDto: AuthForgotPasswordUserDto,
  ) {
    return await this.awsCognitoService.forgotUserPassword(
      authForgotPasswordUserDto,
    );
  }

  @Post('/confirm-password')
  @UsePipes(ValidationPipe)
  async confirmPassword(
    @Body() authConfirmPasswordUserDto: AuthConfirmPasswordUserDto,
  ) {
    return await this.awsCognitoService.confirmUserPassword(
      authConfirmPasswordUserDto,
    );
  }
 ...



이제 새 끝점을 테스트해 보겠습니다.








팔!! 모든 것이 완벽하게 작동하는 것 같습니다.
그게 다야.
이 시리즈가 이 시리즈를 좋아했던 Cognito 및 javascript 통합을 더 잘 이해하는 데 도움이 되기를 바랍니다. 그리고 repohere를 찾을 수 있습니다.

Medium에서 저를 팔로우하거나 제 기술 여정에 대해 자세히 알아보세요.

좋은 웹페이지 즐겨찾기