サーバサイドNestJs、フロントサイドAngularでログイン認証のサンプルを作成

## 概要
サーバサイドはNestJS、フロントサイドはAngularで、JWT(JSON Web Token)を用いたログイン認証のサンプルを作成しました。  
作成を進めながら、NestJSのガード、Augularのガードを用いて、未認証下でのルーティングの制御の動作確認を行いました。  
躓いた個所、確認結果を記します。  
サンプルの全コードは以下に公開しています。  
[GitHub - YamaDash82/nestjs-angular-jwt-auth-sample](https://github.com/YamaDash82/nestjs-angular-jwt-auth-sample)  
もし何方かのWeb開発の助けになることがあればとてもうれしいです。  

## 開発環境  
- Windows10
- WSL2
- Docker
- Node.js 16

## 使用フレームワーク
| フレームワーク | Ver |
|:-:|:-:|
| NestJS  |  9 |
| Angular  | 14  |

## サンプルの概要  
- ログインユーザー情報  
    サーバサイドのメモリ上に保持しています。プログラムを停止すると、新規登録したユーザー情報は消滅します。  
    本番で使用する際はDBに保存するなどの書き換えを行ってください。  
- `http://localhost:4200/login`  
    ログイン画面です。
    ![login.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261708/b0a43000-62e6-52ab-a44e-2b6813e48508.png)
    認証エラー時、サーバサイドから返されたエラーを表示します。  
    ![login-error.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261708/553b2073-72f7-e852-8a45-712c3a93d832.png)
    `APITest`ボタンは未認証状態でWebAPIにリクエストしたとき、サーバサイドでNestJSのガードが働きブロックされることを確認するために配置しました。  
下図はサーバサイドがスローした`UnauthorizedException`の内容を表示しています。
    ![login-api-error.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261708/56ff75a2-5c2a-9096-6745-07e9f68da73c.png)

- `http://localhost:4200`  
    メイン画面です。以下はログイン認証が成功した図です。未認証状態で`http://localhost:4200`にアクセスすると、ログイン画面にリダイレクトします。  
    ![main.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261708/bd18351c-e4c1-5694-5765-24247ba27853.png)
    ログイン有効期間は1分に設定してあります。ログイン有効期間内に再アクセスすると、サーバサイドから有効期間を書き換えたJWTトークンを取得しなおします。

    認証済の状態で`APITest`ボタンをクリックし、WebAPIにリクエストすると、正しくデータが返却されます。  
    下図の`hoge`はWebAPIから返された値を表示しています。  
    ![main-api.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261708/95069fad-5b46-40b9-6349-4807ccd73f99.png)

- `http://localhost:3000/user-registration`  
    新規ユーザー登録画面です。  
    既存のユーザ名を登録しようとすると、サーバサイドがスローしたエラーを表示し、ユーザーにそのユーザー名では登録できないことを伝えます。  
    ![user-rageistration-error.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261708/d8f2488c-e493-6b5b-5f05-d661c1a67e32.png)

## NestJSによるサーバサイドのコーディング  
以下を参考にコーディングしました。
[security-authentication｜NestJS 公式ドキュメントver7日本語訳](https://zenn.dev/kisihara_c/books/nest-officialdoc-jp/viewer/security-authentication)  
関連するファイル数多く、私にとって複雑でしたので整理します。  
下図は各モジュール、クラスの関係を図にしました。番号付きの項目は後述の説明と対応させています。  
![objects-graph.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261708/04715bc4-1734-5abe-3e32-e9791bd4a096.png)

### 主要パッケージ

- dependencies
    - @nestjs/jwt
    - @nestjs/passport
    - passport
    - passport-local
    - passport-jwt
- devDependencies
    - @types/passport-jwt
    - @types/passport-local
    
### ①UsersService (server-side/src/users/users.service.ts)

ユーザー情報を操作するサービスです。当サンプルではユーザー情報を配列で保持しています。本番開発でデータベースを参照する際は、この箇所を変更することになると思います。  
```typescript:server-side/src/users/users.service.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';

export interface User {
  userId: number;
  username: string;
  password: string;
}

@Injectable()
export class UsersService {
  private readonly users: User[] = [
    {
      userId: 1, 
      username: `Taro`, 
      password: `pass1`
    }, 
    { 
      userId: 2, 
      username: `Hanako`, 
      password: 'pass2'
    }, 
  ];

  async findOne(username: string): Promise<User | undefined> {
    return this.users.find(user => {
      return user.username === username
    });
  }

  //新規ユーザー登録処理
  async addNewUser(username: string, password: string): Promise<{userId: number, username: string}> {
    //ユーザー名が既に登録されていないか確認する。
    if (this.users.findIndex(user => { return user.username === username}) >= 0) {
      //既に登録済のユーザーである。
      throw new UnauthorizedException('既に登録済のユーザーです。');
    }

    //ユーザー一覧に追加する。
    const userId = this.users.length + 1;
    this.users.push({ userId, username, password });

    return { userId, username };
  }
}
```
- fineOneメソッド  
    ユーザー名で検索し、ユーザ情報を返します。  
- addNewUserメソッド
    新規ユーザーを追加するメソッドです。  

### ②AuthService (server-side/src/auth/auth.service.ts)  

```typescript:server-side/src/auth/auth.service.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { UsersService } from '../users/users.service';
import { JwtService } from '@nestjs/jwt';

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

  async validateUser(username: string, password: string): Promise<any> {
    const user = await this.usersService.findOne(username);

    if (!user) {
      throw new UnauthorizedException('未登録のユーザーです。');
    }

    if (user.password === password) {
      //spread演算子というらしい。
      const { password, ...result } = user;

      return result;
    } else {
      throw new UnauthorizedException('パスワードが一致しません。');
    }
  }

  async userRegistration(username: string, password: string): Promise<{ access_token: string }> {
    try {
      const newUser = await this.usersService.addNewUser(username, password);
      
      return this.login(newUser);
    } catch(err) {
      throw err;
    }
  }
  
  async login(user: {username: string, userId: number}): Promise<{ access_token: string }> {
    const payload = user;

    return {
      access_token: this.jwtService.sign(payload)
    };
  }
}
```
- validateUserメソッド
    ユーザー名とパスワードを引数で渡し、`①UsersService`を使ってユーザーの検索、パスワードのチェックを行います。  
    該当するユーザー名が存在しなかったとき、ユーザーは見つかったがパスワードが一致しなかったとき、`UnauthorizedException`をスローします。  
    ```typescript
    throw new UnauthorizedException('未登録のユーザーです。');
    ```
    参考サイトでは単に`UnauthorizedException`をスローしていましたが、上記のようにメッセージを追加することで、フロントサイド側でパスワードが一致しなかったのか、そもそもユーザー名が存在しないのかがわかるようにしました。  
    ユーザーの存在、パスワードの一致を確認したら、ユーザー情報を返します。ユーザー情報を返す際、パスワードを除いた情報を返します。  
    以下のようなコーディング法、参考サイトで初めて知りました。
    ```typescript
    // { password, ...result } = user:{ username: string, userId:number, password: string}
    // resultにはUserオブジェクトのpaswordプロパティを除いたオブジェクトが代入される。
    const { password, ...result } = user;
    
    //ユーザー情報のうち、パスワードを除いた情報を返す。
    return result;
    ```
    このvalidateUserメソッドは、後述の`LocalStrategy`の`validate`メソッドから呼び出されます。その`LocalStrategy`は`LocalAuthGuard`というガードに紐づいており、この`LocalAuthGuard`をデコレータで修飾した箇所にアクセスがあった時、`validateUser`メソッドにたどり着き、ユーザー検証処理が行われます。  

- loginメソッド
    渡されたユーザー情報をもとにJWTを生成し、返します。  
    **JWTにパスワードは含めません**。  

### ③LocalStrategy (server-side/src/auth/local.strategy.ts)  

```typescript:server-side/src/auth/local.strategy.ts
import { Strategy } from "passport-local";
import { PassportStrategy } from "@nestjs/passport";
import { Injectable } from '@nestjs/common';
import { AuthService } from "./auth.service";

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
  constructor (private authService: AuthService) {
    super();
  }

  async validate(username: string, password: string): Promise<any> {
    try {
      const user = await this.authService.validateUser(username, password);

      return user;
    } catch (err) {
      throw err;
    }
  }
}
```

- validateメソッド
    引数で渡したusername、passwordを`②AuthService`の`validateUser`メソッドに渡し、ユーザー検証を行います。  

### ④LocalAuthGuard (server-side/src/auth/lobal-auth.guard.ts)  

認証ガードです。③LocalStrategyと紐づいています。    
```typescript:server-side/src/auth/lobal-auth.guard.ts
import { ExecutionContext, Injectable, HttpException, UnauthorizedException } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";

@Injectable()
export class LocalAuthGuard extends AuthGuard('local') { }
```  
`AuthGuard('local') `この記述で、先述の`LocalStrategy`と紐づきます。   
この辺りが私は少々魔法のように感じてしまい、混乱しました。  
> @UseGuards(AuthGuard('local'))では、passport-localストラテジを拡張した際に@nestjs/passportが自動で用意してくれるAuthGuardを利用している。精査してみよう。Passport localストラテジには、デフォルトでは'local'という名前がついている。その名前を@UseGurads()デコレータで参照し、passport-localパッケージで提供されるコードと関連付ける。
([security-authentication｜NestJS 公式ドキュメントver7日本語訳](https://zenn.dev/kisihara_c/books/nest-officialdoc-jp/viewer/security-authentication)より引用)  

このガードをデコレーターで修飾することにより、`LocalStrategy`に紐づき認証処理が行われます。  
```typescript:src/app.controller.ts
@Controller()
export class AppController {
  constructor(private authService: AuthService) {}

  @UseGuards(LocalAuthGuard)  //←デコレータでLoaclAuthGuardを設定
  @Post('auth/login')
  async login(@Request() req) {
    //検証をクリアしたユーザー情報をもとにJWTを発行しフロントに返す。
    return this.authService.login(req.user);
  }
  ...
```
上記のコードですと`localhost:3000/api/login`にPostリクエストがあった時、フロントから渡されたユーザー名、パスワードをもとに`LocalAuthGuard`-`LocalStrategy`-`AuthService`と辿り、ユーザー検証を行います。  
検証をクリアしガードを通過すると、`AppController.login`メソッドの本体の処理が行われます。`return this.authService.login(req.user)`でJWTをフロントに返します。  

### ⑤JwtStrategy (server-side/src/auth/jwt.strategy.ts)

フロントサイドから認証済のユーザーがリクエストを送ってきた際にヘッダに添付されているJWTを解析し、有効なリクエストかを検証します。
```typescript:server-side/src/auth/jwt.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { jwtConstants } from '../constants';

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

  async validate(payload: any) {
    return { userId: payload.userId, username: payload.username };
  }
}
```

### ⑥JwtAuthGuard (server-side/src/auth/jwt-auth.guard.ts)  

JWT検証ガードです。⑤JwtStrategyと紐づいています。  
```typescript:server-side/src/auth/jwt-auth.guard.ts
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') { }
```
`AuthGuard('jwt') `この記述で、先述の`JwtStrategy`と紐づきます。  
このガードをデコレーターで修飾することにより、未認証のユーザーからのリクエストをブロックすることができます。  
認証済ユーザーからのリクエストにはリクエストヘッダに有効なJWTが設定されています。このJWTを検証し、検証を通過すればデコレータで修飾した本体のメソッドに到達します。  


```typescript
@Controller('api')
export class ApiController {
  @UseGuards(JwtAuthGuard) //← デコレータでJwtAuthGuardを設定
  @Get('hoge')
  hoge(): string {
    return 'hoge';
  }
}
```
上記の例ですと、`localhot:3000/hoge`にリクエストしたとき、  
リクエストヘッダのJWTを解析し、JwtStrategyでJWTの有効無効をチェックし、有効であれば、メソッド本体に通し、無効であればブロックします。ブロックした場合、フロントには`UnauthorizedException`例外が返されます。  

## Angularによるフロントサイドのコーディング  
### 主要パッケージ
- @auth0/angular-jwt

### ルーティング
- localhost:4200
    メイン画面です。未認証の状態でアクセスすると後述のガード(AuthGuard)が働き、ログイン画面にリダイレクトします。
- localhost:4200/login  
    ログイン画面です。
- localhost:4200/user-registration
    新規ユーザー登録画面です。  

### AuthService (front-side/src/app/services/auth.service.ts)  
[Morioh \ 【初心者向け】Angular 12 JWT認証](https://morioh.com/p/1070ff189846)  
上記参考サイトのステップ17からを参考にコーディングしました。  

```typescript:front-side/src/app/services/auth.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { throwError } from 'rxjs';
import { map, tap, catchError } from 'rxjs/operators';
import { environment } from 'src/environments/environment';
import { JwtHelperService } from '@auth0/angular-jwt';

const jwt = new JwtHelperService();

class DecodedToken {
  exp!: number;
  username!: string;
  userId!: number;
}

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  private decodedToken: DecodedToken;
  private _loginUser: { username: string, userId: number } | null = null;

  constructor(
    private http: HttpClient
  ) { 
    this.decodedToken = localStorage.getItem('auth_meta') ? JSON.parse(localStorage.getItem('auth_meta') as string) : new DecodedToken();
  }

  async login(username: string, password: string): Promise<void> {
    const url = `${environment.rootUrl}/auth/login`;

    try {
      const token = await (async () => {
        return new Promise((resolve, reject) => {
          this.http.post<{ access_token: string }>(url, {username, password}).pipe(
            catchError((error: HttpErrorResponse) => {
              return throwError(() => new Error(error.error.message));
            }), 
            map(token => {
              return token.access_token
            })
          ).subscribe({
            next: (token) => { return resolve(token); }, 
            error: (err) => { return reject(err); }
          });
        })
      })();

      this.saveToken(token);
    } catch (err) {
      throw err;
    }
  }

  private saveToken(token: any): any {
    try {
      this.decodedToken = jwt.decodeToken(token);
      localStorage.setItem('auth_tkn', token);
      localStorage.setItem('auth_meta', JSON.stringify(this.decodedToken));

      const { exp, ...loginUser } = this.decodedToken;

      this._loginUser = loginUser;
    } catch (err) {
      throw err;
    }

    return token;
  }

  async isAutenticated(): Promise<boolean> {
    if (!this.decodedToken) {
      this.logout();
      return false;
    }
    
    //トークンの保持している有効期限は秒単位、Date#getTime()で取得される値はミリ秒単位のため、Date#getTime()を1000倍して比較する。
    if (this.decodedToken.exp < (new Date().getTime() / 1000)) {
      this.logout();
      return false;
    }

    try {
      const url = `${environment.rootUrl}/check-login`;

      const result = await ((): Promise<boolean> => {
        return new Promise((resolve, reject) => {
          //JwtModuleにより自動でヘッダーにJWTトークンが設定される。
          this.http.get<{ access_token: string }>(url).pipe(
            catchError((err: HttpErrorResponse) => {
              return throwError(() => { new Error(err.error.message); });
            }), 
            tap(data => {
              if(!("access_token" in data)) {
                throw new Error('トークンが取得できませんでした。');
              }

              this.saveToken(data.access_token);
            })
          ).subscribe({
            next: _ => { return resolve(true); }, 
            error: err => { return reject(err); }
          })
        });
      })();
      
      return result;
    } catch (err) {
      return false;
    }
  }

  logout() {
    localStorage.removeItem('auth_tkn');
    localStorage.removeItem('autn_meta');

    this.decodedToken = new DecodedToken();
    this._loginUser = null;
  }

  async registerUser(
    username: string, 
    password: string
  ) {
    const url = `${environment.rootUrl}/user-registration`;

    try {
      const result = await (async (): Promise<boolean> => {
        return new Promise((resolve, reject) => {
          this.http.post<{ access_token: string }>(url, { username, password}).pipe(
            catchError((err: HttpErrorResponse) => {
              return throwError(() => new Error(err.error.message));
            }), 
            map(data => {
              return data.access_token;
            })
          ).subscribe({
            next: token => {
              this.saveToken(token);
              return resolve(true);
            }, 
            error: err => { return reject(err); }
          })
        });
      })();

      return result;
    } catch (err) {
      throw err;
    }
  }

  get loginUser(): { username: string, userId: number } | null {
    return this._loginUser;
  }
}
```

- loginメソッド
    ユーザーが入力したユーザー名、パスワードを`localhost:3000/auth/login`にリクエストし、サーバサイドからの結果を取得します。  
    サーバサイドで認証が通過すると、JWTが返されるので後述の`saveToken`メソッドを呼び出し、ローカルストレージにJWTとJWTをデコードした内容(usernameとuserId)を保存します。  
    認証が通過しなかった場合、サーバがスローした`UnautorizedException`例外をキャッチします。この例外には未登録のユーザーなのか、パスワードが一致しなかったのかのメッセージを設定されているので、これをコンポーネントに表示させることでユーザーに内容を伝えることができます。  

- saveTokenメソッド  
    loginで認証が通過したときに呼び出され、サーバサイドから返されたJWTとJWTをデコードした内容をローカルストレージに保存します。  
    JWTは`auth_tkn`というキー、デコードしたユーザー情報は`auth_meta`というキーで保存しています。   
    サーバサイドにリクエストする際の、リクエストヘッダにセットするJWTは、ここで保存したローカルストレージから取得します。  

- isAutenticatedメソッド
    認証済か否かを確認するメソッドです。後述の認証ガード`AuthGuard`から呼び出されます。このガードをメインコンポーネントに設定するので、アプリ起動時に認証チェックを行います。  
    認証済の場合、サーバサイドが有効期間を設定しなおした新しいJWTを返します。これをsaveTokenメソッドで保存します。  

**躓き箇所**  
`isAutenticated`メソッドでJWTの有効期限と現在時刻を比較する箇所で躓きました。 
当初以下のようにしており、有効期限内のはずなのに、常に有効期限切れと判定され、何故?と思っておりました。
```typescript
if (this.decodedToken.exp < new Date().getTime()) {
  this.logout();
  return false;
}
``` 
JWTに含まれる有効期限は`1970/01/01 00:00:00`を起点とした**秒単位**、`new Date().getTime()`が返す値は`1970/01/01 00:00:00`を起点とした**ミリ秒単位**です。  
つまり値としては`new Date().getTime()`が返す値は3ケタ多く、`(this.decodedToken.exp < new Date().getTime())`は常に`true`を返し有効期限切れと判定されていました。
正しく秒単位で比較するように、`new Date().getTime()`を1000で割り秒単位で比較することで正しく判定されるようにしました。  
```typescript
if (this.decodedToken.exp < (new Date().getTime() / 1000)) {
  this.logout();
  return false;
}
```

### `HttpClient`によるリクエストに自動でJWTがセットされるようにする設定  
[GitHub - auth0/angular2-jwt: Helper library for handling JWTs in Angular 2+ apps](https://github.com/auth0/angular2-jwt)  
上記の`Usage: Injection`の節を参考にコーディングしました。 


```typescript:front-side/src/app/app.module.ts
...
@NgModule({
  ...
  imports: [
    ...
    JwtModule.forRoot({
      config: {
        tokenGetter: () => {
          const token = localStorage.getItem("auth_tkn");
          return token;
        }, 
        //`http://localhost:3000`と指定したら、httpClientを用いてのリクエスト時、上記tokenGetterを通らなかった。
        allowedDomains: ['localhost:3000']
      }
    })
    ...
  ], 
  ...
})
export class AppModule { }
```

`config.tokenGetter`にJWTを取得するコードを記述します。`AuthService.saveToken`メソッドで`auth_tkn`というキーで保存しているので、これを取得し返します。  
この設定により、HttpClientを使ったリクエストに自動でJWTがセットされるようになります。  
`config.allowedDomains`にリクエストを送信するドメインを記述します。  
> Note: If requests are sent to the same domain that is serving your Angular application, you do not need to add that domain to the allowedDomains array. However, this is only the case if you don't specify the domain in the Http request.
(Google翻訳:)注:allowedDomains Angular アプリケーションを提供しているドメインと同じドメインにリクエストが送信された場合、そのドメインを配列に追加する必要はありません。Httpただし、これはリクエストでドメインを指定しない場合にのみ当てはまります。
当サンプルでは開発時の仮サーバーのドメインをハードコーディングしているので本番開発時には書き換えが必要です。  
([GitHub - auth0/angular2-jwt: Helper library for handling JWTs in Angular 2+ apps](https://github.com/auth0/angular2-jwt)より引用)

開発時の仮サーバー、サーバサイドは`localhost:3000`、クライアントサイドは`localhost:4200`でポート番号が異なり、異なるドメイン扱いになるので、`allowedDomains`を設定しています。ハードコーディングしているので、本番開発時には書き換えが必要です。  

**躓き箇所** 
`auth0/angular2-jwt`のドキュメントにある通り、リクエストにJWTがセットされず、何故だ?と思っていたことがありました。以下でした。
```typescript
//誤
allowedDomains: ['http://localhost:3000']
//正
allowedDomains: ['localhost:3000']
```

### AuthGuardにて未認証時のルーティングをガード  

#### AuthGuard (front-side/src/app/gurards/auth.gurard.ts)

```typescript:front-side/src/app/gurards/auth.gurard.ts
@Injectable({
  providedIn: 'root'
})
export class AuthGuard implements CanActivate {
  constructor (
    private auth: AuthService, 
    private router: Router, 
  ) {}

  async canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Promise<boolean> {
    
    const autenticated = await this.auth.isAutenticated();
    
    if (autenticated) {
      return true;
    } else {
      this.router.navigate(['/login']);

      return false;
    }
  }
}
```

`canActive`メソッドで、`AuthService.isAuthenticated`メソッドを呼び出し、認証チェックを行います。認証済であれば`true`を返し、未認証であればログイン画面にリダイレクトし`false`を返します。


#### ルーティングテーブルにAuthGuardを設定  


```typescript:front-side/src/app/app-routing.module.ts
...
const routes: Routes = [
  { path: '', 
    component: MainComponent, 
    canActivate: [ AuthGuard ] //←ガードの設定
   }, 
  { path: 'login', component: LoginComponent }, 
  { path: 'user-registration', component: UserRegistrationComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }
```
`MainComponent`にルーティングするパス`path:''`(`localhost:4200`)に`AuthGuard`を設定しています。これにより`AuthGuard.canActive`メソッドが`true`を返せば`MainComponent`を描画し、`false`を返せば`path:''`へのアクセスはブロックされ、`AuthGuard.canActive`メソッドの処理によりログイン画面にリダイレクトします。  

## 注意点
当サンプルコードでは、JWTの暗号化キーをプログラム内に定数として記述しています。  
本番開発時は、外部に漏れないように外部ファイルや、環境変数に保持するように書き換えてください。  
> WARNING
この鍵を公開するべきではない。ここではコードが何をしているかを明確にする為公開しているが、実運用システムではsecrets valut、環境変数、設定サービスなどの適切な手段を用いて鍵を保護しなければならない。  
([security-authentication｜NestJS 公式ドキュメントver7日本語訳](https://zenn.dev/kisihara_c/books/nest-officialdoc-jp/viewer/security-authentication)より引用)

暗号化キー設定箇所は以下です。
```typescript:server-side/src/constants.ts
export const jwtConstants = {
  secret: 'secret key'
};
```
上記定数値参照箇所は以下です。
```typescript:server-side/auth/auth.module.ts
@Module({
  imports: [
    UsersModule, 
    PassportModule, 
    JwtModule.register({
      secret: jwtConstants.secret, //←暗号化キー参照個所
      signOptions: { expiresIn: '60s' }
    })
  ], 
  ...
```
```typescript:server-side/auth/jwt.strategy.ts
...
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 
      ignoreExpiration: false, 
      secretOrKey: jwtConstants.secret  //←暗号化キー参照個所
    });
  }
...
```
## 疑問点  
JWTについて調べると、秘密鍵と公開鍵を利用しているとのことですがどこで鍵の設定をしているのかわかっていません。  
前述のJwtModuleをimportしている個所のsecretの設定は秘密鍵の暗号化キーの設定なのでしょうか、では公開鍵は...?  
これから調べます。
現時点ではライブラリがうまい具合に処理してくれている...という認識です。  

## 感想  
ログイン認証というと、過去に`Express`、`passport`を利用して、セッション、cookieを使う方法を作ったことがあるのですが、この度JWTを使う方法、もとい、JWTそのものを初めて知りました。  
きっかけは、Node.JSによるサーバサイドのコーディングを`Express`から`NestJS`に切り替えました。NestJSによる認証を作ろうと公式とその日本語訳サイトを確認したところ、それがJWTを用いた方法でした。  
それでJWTとは何かを調べようとGoogleに打ち込むと"JWT 使うな"という検索候補が上がったりしていきなりカウンタージャブでしたが、とりあえず進めました。  
もちろん何か危険性があっての"JWT 使うな"だと思いますので、JWTを使うリスクについてはこれから調べます。
サーバサイドのコーディングで`○○Strategy`、`○○Guard`あたりの関連を理解するのに苦労しましたが、認証制御したい箇所にJwtAuthGuardをデコレータで修飾すればよく、便利だと思いました。  
サンプルのコーデイングが終わってから記事を書き始めましたが、記事を書きながら理解不足している箇所を確認することができました...いや、まだボヤっとしているところも...
とはいえログイン認証、未認証時のブロックの動作確認ができたとき、期待した挙動になった時は達成感有りました。

以上です。

## 参考にさせていただいた記事、サイト  
[Documentation \| NestJS - A progressive Node.js framework](https://docs.nestjs.com/security/authentication)  
[security-authentication｜NestJS 公式ドキュメントver7日本語訳](https://zenn.dev/kisihara_c/books/nest-officialdoc-jp/viewer/security-authentication)
翻訳感謝です    
[GitHub - auth0/angular2-jwt: Helper library for handling JWTs in Angular 2+ apps](https://github.com/auth0/angular2-jwt)  
[Morioh \ 【初心者向け】Angular 12 JWT認証](https://morioh.com/p/1070ff189846)  
[JWT の仕組み](https://zenn.dev/mikakane/articles/tutorial_for_jwt)

ありがとうございました。