2020-3-6 NestJS에서 JWT인증
Authentication
- nest에서 권한패키지로 passport를 많이쓴다. @nestjs/passport
- passport 실행순서
- user의 credentials을 확인한다.(id,password를 확인 or JWT or identtiy token)
- authentication 상태를 관리
- 사용자의 request에 authentication을 붙인다.
$npm install --save @nestjs/passport passport passport-local
$npm i --save-dev @types/passport-local
- passport전략을 선택할때 @nestjs/passport와 passport패키지가 필요하고 그런 다음 구체적인 strategy-specific package(passport-jwt, passport-local)을 설치해야한다.
implementing passport strategies
- passport자체를 하나의 미니 프레임워크로 볼 수 있는데, @nestjs/passport 모듈이 nestjs스타일로 wrapping해준다.
- vanilla passport 동작원리
- set of option들이 passport의 전략을 구체적으로 명시한다. jwt 전략에서는 sign token을 제공한다.
- ‘verify callback’는 어떻게 반응을 하는지 명시. passport라이브러리는 callback에서 validation이 true이면 user를 리턴해준다. false이면 null을 리턴해준다.
- Passport전략을 PassportStrategy클라스를 확장해서 설정한다. 서브클래스에서 super()메서드를 호출하고 선택적으로 옵션을 전달한다. 또한 validate()메서드를 구현하여 verifiy callback을 제공해야한다.
@Injectable()
export class AuthService {
constructor(private readonly accountsService: AccountsService) {}
async validateAccount(id: string, email: string, password: string): Promise<any> {
const account: Account = await this.accountsService.findOne(id);
console.log(`validateAccount에서 account ${JSON.stringify(account)}`);
if (account && account.getPassword() == password) {
const {getPassword, ...result} = account;
return result;
}
return null;
}
}
- 실제 애플리케이션에서는 password대신 salt와 함께 단방향으로 암호화된 bcrpyt를 사용해야한다.
- 그러면 hash화된 password끼리 매칭을 하는것이다.
built-in passport guard
- guard는 reuqest가 router에서 handle이 되는지 안되는지를 결정한다.
- 앱은 인증관점에서 2가지 상태로 존재할 수 있다.
- user/clinet is not logged in (not authenticated)
- user/clinet logged in(is authenticated)
- 로그인이 안된상태에서는 2가지 고유의 함수들이 필요하다.
- Unauthenticated user가 접근하는것을 막아야한다. Deny access. guard를 통해 routes를 보호할 수 있다. 여기서 JWT가 유효한지를 체크한다.
- authentication단계를 시작할때, 어떻게 passport-local strategy를 라우팅할지? POST username/password or POST /auth/login이 떠오르지만. @nestjs/passport에서 다른종류의 guard를 사용해야한다.
login route
- /auth/login을 구현해야한다.
import { Controller, Request, Post, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Controller()
export class AppController {
@UseGuards(AuthGuard('local'))
@Post('auth/login')
async login(@Request() req) {
return req.user;
}
}
// local.strategy.ts
@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private readonly authService: AuthService) {
super({
usernameField: 'email',
password: 'password',
});
}
// @UseGuard에서('local')을 Strategy로 선택한 controller에 대해서 동작
// 처음 request가 들어가면 request에 대한 검증을 한다.
async validate(email: string, password: string): Promise<any> {
console.log(`local strategy validate`);
const payload: JwtPayload = {email, password};
const account = await this.authService.validateAccount(payload);
if (!account)
throw new UnauthorizedException('Fail to login try again');
return account;
}
}
- @UseGuards(AuthGuard(‘local’))은 passport-local전략을 사용할때, @nestjs/passport에서 규정한 AuthGuard를 사용하겠다는 의미다.
- passport의 기본적인 local전략의 이름은 ‘local’이다?
- /auth/login라우터는 user를 리턴해준다. 여기서 passport의 특징이 드러나는데, user객체를 자동으로 생성하고 validate()로부터 받은 return값을 돌려주는것이다. 뿐만아니라 Request객체에 req.user로서 할당된다.
- @UseGuard(AuthGuard(‘local’))이 붙은 라우터에 요청에서는 validate을 우선적으로 통과한다.
Written on March 6, 2020