NestJS で JWT を使ったパスワード認証

次の記事と同じことを行いました。
[NestJS で簡易的なパスワード認証を実装する](https://qiita.com/ci7lus/items/4b481d1ae670fba7e137)
>ユーザー名が admin、パスワードが password のときのみ認可されます。

サーバーの構築

```bash
nest new authentication
cd authentication
nest g module admin
nest g service admin
nest g controller admin
```

最終的に src は次のようになります。

```text
$ tree src
src
├── admin
│   ├── admin.controller.spec.ts
│   ├── admin.controller.ts
│   ├── admin.module.ts
│   ├── admin.service.spec.ts
│   ├── admin.service.ts
│   ├── jwt.strategy.ts
│   ├── local.strategy.ts
│   └── secrets.ts
├── app.controller.spec.ts
├── app.controller.ts
├── app.module.ts
├── app.service.ts
└── main.ts
```

```ts:src/admin/local.strategy.ts
import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super();
  }

  validate(username: string, password: string): boolean {
    if (username === 'admin' && password == 'password') {
      return true;
    } else {
      throw new UnauthorizedException();
    }
  }
}
```

```ts:src/admin/admin.module.ts
import { Module } from '@nestjs/common';
import { AdminService } from './admin.service';
import { PassportModule } from '@nestjs/passport';
import { LocalStrategy } from './local.strategy';
import { JwtModule } from '@nestjs/jwt';
import { AdminController } from './admin.controller';
import { JwtStrategy } from './jwt.strategy';
import { JWT_SECRET_KEY } from './secrets';

@Module({
    imports: [
	PassportModule,
	JwtModule.register({
      secret: JWT_SECRET_KEY,
      signOptions: { expiresIn: '600s' },
    }),
],
  providers: [AdminService, LocalStrategy, JwtStrategy],
  controllers: [AdminController]
})
export class AdminModule {}
```

```ts:src/admin/admin.controller.ts
import { Controller, UseGuards, Post, Get  } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AdminService } from './admin.service';

@Controller('admin')
export class AdminController {
  constructor(private readonly adminService: AdminService) {}

  @UseGuards(AuthGuard('local'))
  @Post('/login')
  login() {
    return this.adminService.sign();
  }

  @UseGuards(AuthGuard('jwt'))
  @Get('/status')
  status() {
    return true;
  }

}
```

```ts:src/admin/secrets.ts
export const JWT_SECRET_KEY = 'secret';
```

```ts:src/admin/jwt.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { JWT_SECRET_KEY } from './secrets';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: JWT_SECRET_KEY,
    });
  }

  async validate(payload: { isAdmin: boolean }) {
    if (payload.isAdmin === true) {
      return true;
    } else {
      return false;
    }
  }
}
```

```ts:src/admin/admin.service.ts
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';

@Injectable()
export class AdminService {
  constructor(private readonly jwtService: JwtService) {}
  sign() {
    return { access_token: this.jwtService.sign({ isAdmin: true }) };
  }

}
```

ライブラリーのインストール

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

サーバーの起動

```bash
npm start
```

Httpie を使ったテストスクリプト

```bash:go_http.sh
URL_ADMIN="http://localhost:3000/admin"
#
http post $URL_ADMIN/login username="admin" password="password" > token.json
#
token=`jq .access_token token.json | sed 's/"//g'`
#
http get $URL_ADMIN/status "Authorization:Bearer ${token}"
```

Curl を使ったテストスクリプト

```bash:go_curl.sh
URL_ADMIN="http://localhost:3000/admin"
#
curl -X POST $URL_ADMIN/login -d username="admin" -d password="password" > token.json
#
token=`jq .access_token token.json | sed 's/"//g'`
#
curl $URL_ADMIN/status -H "Authorization:Bearer ${token}" | jq .
```