NestJS JWT MIDDLEWARE


MIDDLEWARE


まず、jwtフォルダにはjwtが含まれます.middleware.tsファイルを作成します.
ミドルウェアをクラスとして定義したり、関数として定義したりできます.
nestjsのミドルウェアはexpressと同じなのでnext()が必要です.
import { NestMiddleware } from '@nestjs/common';
import { NextFunction, Request, Response } from 'express';

//implements는 해당 클래스가 interface로 행동하도록 한다.
// export class JwtMiddleware implements NestMiddleware {
//   use(req: Request, res: Response, next: NextFunction) {
//     console.log(req.headers);
//     next();
//   }
// }

export function jwtMiddleware(req: Request, res: Response, next: NextFunction) {
  console.log(req.headers);
  next();
}

使用


Applying middleware@Module()  Decoratorにはミドルウェアの位置がありません.モジュールクラスの  configure()  メソッドを使用して設定します.ミドルウェアを含むモジュールは、次のとおりです.  NestModule  インタフェースを実装する必要があります.  AppModule  レベル#レベル#  設定jwtMiddleware
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { jwtMiddleware } from './jwt/jwt.middleware';

@Module({
  ...
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(jwtMiddleware)
      .forRoutes({ path: '/graphql', method: RequestMethod.ALL });
  }
}
ユーザがpathとして指定されたurlにリクエストを送信すると、ミドルウェアは正常に動作します.
Global middleware
すべての登録済みパスにミドルウェアを一度にバインドするには  INestApplication  インスタンスが提供する  use()  メソッドを使用できます.
app.useは、パラメータとしての値がfunctionの場合にのみ使用できます.
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { jwtMiddleware } from './jwt/jwt.middleware';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  app.use(jwtMiddleware);
  await app.listen(3000);
}
bootstrap();

jwtミドルウェアの作成


users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateAccountInput } from './dtos/create-account.dto';
import { LoginInput } from './dtos/login.dto';
import { User } from './entities/user.entity';
import { ConfigService } from '@nestjs/config';
import { JwtService } from 'src/jwt/jwt.service';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly users: Repository<User>,
    private readonly config: ConfigService,
    private readonly jwtService: JwtService,
  ) {}

  //create User
	 ...
  //Login User
	...
  async findById(id: number): Promise<User> {
    return this.users.findOne({ id });
  }
}
まず,idでユーザを検索する方法を作成する.
users.module.ts
ユーザー・サービスをエクスポートして、他の場所から依存性を注入します.
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { UsesrResolver } from './users.resolver';
import { UsersService } from './users.service';

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  providers: [UsesrResolver, UsersService],
  exports: [UsersService],
})
export class UsersModule {}
jwt/jwt.service.ts
ユーザがサーバからトークンを受信と、 サーバにリクエストを送信する場合、request.タグを含む要求をHeaderに送信します.
その後、サーバは受信したトークンが有効かどうかを確認します.
jwt.verify()関数を使用してトークンの有効性をチェックできます.
jwt.verify()関数には、次のパラメータが含まれます.
最初のtoken:clientから受信したtoken
2番目のsecretkey:tokenの作成時に使用するsecretkey
import { Inject, Injectable } from '@nestjs/common';
import * as jwt from 'jsonwebtoken';
import { JwtModuleOptions } from './jwt.interfaces';
import { CONFIG_OPTIONS } from './jwt.constants';

@Injectable()
export class JwtService {
  constructor(
    @Inject(CONFIG_OPTIONS)
    private readonly options: JwtModuleOptions,
  ) {
    console.log(this.options);
  }
  sign(userId: number): string {
    return jwt.sign({ id: userId }, this.options.privateKey);
  }
  verify(token: string) {
    return jwt.verify(token, this.options.privateKey);
  }
}
jwt/jwt.middleware.ts
if(「x-jwt」in req.headers)でヘッダにクライアントから与えられたx-jwtがあるかどうかを確認すると、
jwtServiceのverifyメソッドにより、jwt.signに渡されるtoken値を取得できます.
コンソールに復号を書き込もうとすると、{id:3、iat:16362850}が得られます.
decodeにidという構成がある場合は、userServiceのfindByIdメソッドでユーザーを検索できます.
import { Injectable, NestMiddleware } from '@nestjs/common';
import { NextFunction, Request, Response } from 'express';
import { UsersService } from 'src/users/users.service';
import { JwtService } from './jwt.service';

// implements는 해당 클래스가 interface로 행동하도록 한다.
@Injectable()
export class JwtMiddleware implements NestMiddleware {
  constructor(
    private readonly jwtService: JwtService,
    private readonly userService: UsersService,
  ) {}
  async use(req: Request, res: Response, next: NextFunction) {
    if ('x-jwt' in req.headers) {
      const token = req.headers['x-jwt'];
      try {
        const decoded = this.jwtService.verify(token.toString());
        if (typeof decoded === 'object' && decoded.hasOwnProperty('id')) {
          const user = await this.userService.findById(decoded['id']);
          // 이 request는 HTTP request같은건데 이걸 graphql resolver에 전달해 줘야한다.
          req['user'] = user;
        }
      } catch (e) {}
    }
    next();
  }
}
req['user']=userこのrequestはHTTP requestと類似しており、graphsql解析器に渡す必要があります.
req.user=userはreq[「user」]=userではなく、objキーを動的に使用する場合にobj[key]として使用されるためです.また、reqには元のユーザーというプロファイルがないため、タイプスクリプトにエラーが表示されます.

GraphQLModule context


GraphQLModule.forRootの場合はcontextを使用できます
contextがフォースとして定義されると、各リクエストが呼び出されます.
contextはexpressからreq属性を含むオブジェクトを受信する.
すなわち,ミドルウェアに渡されるreq['user']=userの値はcontextである.userに渡される値.
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { jwtMiddleware } from './jwt/jwt.middleware';

@Module({
	...
  GraphQLModule.forRoot({
      autoSchemaFile: true, //메모리에 저장
      //request context는 각 request에서 사용이 가능하다
      //context기 힘수로 정의되면 매 request마다 호출된다.
      //이것은 req property를 포함한 object를 express로부터 받는다.
      context: ({ req }) => ({ user: req['user'] }),
   }),
	...
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(jwtMiddleware)
      .forRoutes({ path: '/graphql', method: RequestMethod.ALL });
  }
}

Guard


nestg mo authでauthモジュールを作成します.
auth/auth.guard.tsファイルの作成
canActivateは関数で、trueを返すと要求が続行され、falseの場合は要求が停止します.
要求されたコンテキストには、canActivate(コンテキスト:ExecutionContext)のコンテキストでアクセスできます.
しかし問題はcontextがhttpであることです.だからこれをGraphqlに変えます
GqlExecutionContext.create(context).graphqlコンテキスト値はgetContext()で取得できます.
user gisではreturn trueに設定してrequestを行い、userが存在しない場合は戻り値をfalseに設定してrequestを停止します.
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';

// guard는 함수인데 request를 다음 단계로 진행할지 말지 결정한다.
// CanActivate는 함수인데 true를 리턴하면 request를 진핼시키고 false면 request를 멈추게한다
@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext) {
    const gqlContext = GqlExecutionContext.create(context).getContext();
    const user = gqlContext['user'];
    if (!user) {
      return false;
    }
    return true;
  }
}
Guardの使用
@UseGuardsのパラメータを使用すると、ミドルウェアによって作成されたAuthGuardを使用できます.
これで、Queryにmeを要求すると、トークン値がある場合は要求を続行し、ない場合は要求を停止します.
import { UseGuards } from '@nestjs/common';
import { Resolver, Query, Mutation, Args, Context } from '@nestjs/graphql';
import { AuthGuard } from 'src/auth/auth.guard';
import {
  CreateAccountInput,
  CreateAccountOutput,
} from './dtos/create-account.dto';
import { LoginInput, LoginOutput } from './dtos/login.dto';
import { User } from './entities/user.entity';
import { UsersService } from './users.service';

@Resolver((of) => User)
export class UsesrResolver {
  constructor(private readonly usesrService: UsersService) {}
	...
  @Query((returns) => User)
  @UseGuards(AuthGuard)
  me() {}
}
AuthUser Decoratorの作成
auth/auth-user.decorator.tsファイルを作成します.
createParamDecoratorには工場機能が必要です.
factory関数には、常に未知の値のデータとコンテキストが含まれます.
以前にGuardを作成したときに使用したコンテキストを使用してuserをロードして返します.
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';

export const AuthUser = createParamDecorator(
  (data: unknown, context: ExecutionContext) => {
    const gqlContext = GqlExecutionContext.create(context).getContext();
    const user = gqlContext['user'];
    return user;
  },
);
AuthUser Decoratorの使用
私たちが作成したAuthUserは、関数のパラメータに入れて使用できます.
authUserが返す値はauthUserに入ります.
import { UseGuards } from '@nestjs/common';
import { Resolver, Query, Mutation, Args, Context } from '@nestjs/graphql';
import { AuthGuard } from 'src/auth/auth.guard';
import {
  CreateAccountInput,
  CreateAccountOutput,
} from './dtos/create-account.dto';
import { LoginInput, LoginOutput } from './dtos/login.dto';
import { User } from './entities/user.entity';
import { UsersService } from './users.service';

@Resolver((of) => User)
export class UsesrResolver {
  constructor(private readonly usesrService: UsersService) {}
	...
  @Query((returns) => User)
  @UseGuards(AuthGuard)
  me(@AuthUser() authUser: User) {
    //AuthUser에서 return한 값이 authUser에 들어간다.
    console.log(authUser);
    return authUser;
  }
}