LoginSignup
2
2

More than 1 year has passed since last update.

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

Posted at

概要

サーバサイドはNestJS、フロントサイドはAngularで、JWT(JSON Web Token)を用いたログイン認証のサンプルを作成しました。
作成を進めながら、NestJSのガード、Augularのガードを用いて、未認証下でのルーティングの制御の動作確認を行いました。
躓いた個所、確認結果を記します。
サンプルの全コードは以下に公開しています。
GitHub - 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
    認証エラー時、サーバサイドから返されたエラーを表示します。
    login-error.png
    APITestボタンは未認証状態でWebAPIにリクエストしたとき、サーバサイドでNestJSのガードが働きブロックされることを確認するために配置しました。
    下図はサーバサイドがスローしたUnauthorizedExceptionの内容を表示しています。
    login-api-error.png

  • http://localhost:4200
    メイン画面です。以下はログイン認証が成功した図です。未認証状態でhttp://localhost:4200にアクセスすると、ログイン画面にリダイレクトします。
    main.png
    ログイン有効期間は1分に設定してあります。ログイン有効期間内に再アクセスすると、サーバサイドから有効期間を書き換えたJWTトークンを取得しなおします。

    認証済の状態でAPITestボタンをクリックし、WebAPIにリクエストすると、正しくデータが返却されます。
    下図のhogeはWebAPIから返された値を表示しています。
    main-api.png

  • http://localhost:3000/user-registration
    新規ユーザー登録画面です。
    既存のユーザ名を登録しようとすると、サーバサイドがスローしたエラーを表示し、ユーザーにそのユーザー名では登録できないことを伝えます。
    user-rageistration-error.png

NestJSによるサーバサイドのコーディング

以下を参考にコーディングしました。
security-authentication|NestJS 公式ドキュメントver7日本語訳
関連するファイル数多く、私にとって複雑でしたので整理します。
下図は各モジュール、クラスの関係を図にしました。番号付きの項目は後述の説明と対応させています。
objects-graph.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)

ユーザー情報を操作するサービスです。当サンプルではユーザー情報を配列で保持しています。本番開発でデータベースを参照する際は、この箇所を変更することになると思います。

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)

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をスローします。

    throw new UnauthorizedException('未登録のユーザーです。');
    

    参考サイトでは単にUnauthorizedExceptionをスローしていましたが、上記のようにメッセージを追加することで、フロントサイド側でパスワードが一致しなかったのか、そもそもユーザー名が存在しないのかがわかるようにしました。
    ユーザーの存在、パスワードの一致を確認したら、ユーザー情報を返します。ユーザー情報を返す際、パスワードを除いた情報を返します。
    以下のようなコーディング法、参考サイトで初めて知りました。

    // { password, ...result } = user:{ username: string, userId:number, password: string}
    // resultにはUserオブジェクトのpaswordプロパティを除いたオブジェクトが代入される。
    const { password, ...result } = user;
    
    //ユーザー情報のうち、パスワードを除いた情報を返す。
    return result;
    

    このvalidateUserメソッドは、後述のLocalStrategyvalidateメソッドから呼び出されます。そのLocalStrategyLocalAuthGuardというガードに紐づいており、このLocalAuthGuardをデコレータで修飾した箇所にアクセスがあった時、validateUserメソッドにたどり着き、ユーザー検証処理が行われます。

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

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

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を②AuthServicevalidateUserメソッドに渡し、ユーザー検証を行います。

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

認証ガードです。③LocalStrategyと紐づいています。

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日本語訳より引用)

このガードをデコレーターで修飾することにより、LocalStrategyに紐づき認証処理が行われます。

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を解析し、有効なリクエストかを検証します。

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と紐づいています。

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を検証し、検証を通過すればデコレータで修飾した本体のメソッドに到達します。

@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認証
上記参考サイトのステップ17からを参考にコーディングしました。

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の有効期限と現在時刻を比較する箇所で躓きました。
当初以下のようにしており、有効期限内のはずなのに、常に有効期限切れと判定され、何故?と思っておりました。

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で割り秒単位で比較することで正しく判定されるようにしました。

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
上記のUsage: Injectionの節を参考にコーディングしました。

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より引用)

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

躓き箇所
auth0/angular2-jwtのドキュメントにある通り、リクエストにJWTがセットされず、何故だ?と思っていたことがありました。以下でした。

//誤
allowedDomains: ['http://localhost:3000']
//正
allowedDomains: ['localhost:3000']

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

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

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を設定

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日本語訳より引用)

暗号化キー設定箇所は以下です。

server-side/src/constants.ts
export const jwtConstants = {
  secret: 'secret key'
};

上記定数値参照箇所は以下です。

server-side/auth/auth.module.ts
@Module({
  imports: [
    UsersModule, 
    PassportModule, 
    JwtModule.register({
      secret: jwtConstants.secret, //←暗号化キー参照個所
      signOptions: { expiresIn: '60s' }
    })
  ], 
  ...
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の設定は秘密鍵の暗号化キーの設定なのでしょうか、では公開鍵は...?
これから調べます。
現時点ではライブラリがうまい具合に処理してくれている...という認識です。

感想

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

以上です。

参考にさせていただいた記事、サイト

Documentation | NestJS - A progressive Node.js framework
security-authentication|NestJS 公式ドキュメントver7日本語訳
翻訳感謝です
GitHub - auth0/angular2-jwt: Helper library for handling JWTs in Angular 2+ apps
Morioh \ 【初心者向け】Angular 12 JWT認証
JWT の仕組み

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

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2