해결된 질문
작성
·
767
·
수정됨
0
강사님 안녕하세요?
토큰 재발급 API 실습 중에 해결되지 않는 부분이 있어 질문드려요.
관련 코드는 강의에서 진행하는 대로 모두 작성하였고, 마지막 실습 부분에서 막힙니다.
login 과
fetchUser 까지는 진행이 잘 되는데
문제는,
restoreAccessToken 부분에서 401 에러가 발생합니다.
관련 에러 명령 프롬프트 화면입니다.
관련 쿠키 값 입니다.
(login 시도 시 쿠키 값)
코드는 실습대로 다 작성했구요.
실습도 그대로 따라하는 중 restoreAccessToken 부분만 에러가 나네요.
auth.resolver.ts 의 @UseGuards 데코레이터를 빼보기도 하고, 제 나름대로 해결책을 찾아보려 했는데 잘 모르겠네요. 구글링 해봐도 안되고,
혹시 제가 빼먹은 부분이나 잘못한 부분이 있을까요?
도움 부탁드립니다 :)
아래는 관련 제 코드들 입니다.
auth.module.ts
import { Module } from '@nestjs/common';
import { AuthResolver } from './auth.resolver';
import { AuthService } from './auth.service';
import { JwtModule } from '@nestjs/jwt';
import { UserService } from '../users/user.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from '../users/entities/user.entity';
import { JwtRefreshStrategy } from 'src/commons/auth/jwt-refresh.strategy';
@Module({
imports: [
JwtModule.register({}), //
TypeOrmModule.forFeature([User]),
],
providers: [
JwtRefreshStrategy, //
AuthResolver,
AuthService,
UserService,
],
})
export class AuthModule {}
auth.resolver.ts
import { UnprocessableEntityException, UseGuards } from '@nestjs/common';
import { Args, Context, Mutation, Resolver } from '@nestjs/graphql';
import { UserService } from '../users/user.service';
import * as bcrypt from 'bcrypt';
import { AuthService } from './auth.service';
import { GqlAuthRefreshGuard } from 'src/commons/auth/gql-auth.guard';
import { CurrentUser } from 'src/commons/auth/gql-user.param';
@Resolver()
export class AuthResolver {
constructor(
private readonly userService: UserService, //
private readonly authService: AuthService,
) {}
@Mutation(() => String)
async login(
@Args('email') email: string, //
@Args('password') password: string,
@Context() context: any,
) {
// 1. 로그인(이메일이 일치하는 유저를 DB에서 찾기)
const user = await this.userService.findOne({ email });
// 2. 일치하는 이메일이 없으면 -> 에러 던지기!!
if (!user) throw new UnprocessableEntityException('이메일이 없습니다.');
// 3. 일치하는 이메일이 있지만, 비밀번호가 틀렸다면 -> 에러 던지기!!
const isAuth = await bcrypt.compare(password, user.password);
if (!isAuth) throw new UnprocessableEntityException('암호가 틀렸습니다.');
// 4. refreshToken(=JWT)을 만들어서 프론트엔드(쿠키)에 보내주기
this.authService.setRefreshToken({ user, res: context.res });
// 5. 이메일과 비밀번호 모두 일치한다면 -> accessToken(=JWT)을 만들어서 브라우저에 전달하기
return this.authService.getAccessToken({ user });
}
@UseGuards(GqlAuthRefreshGuard)
@Mutation(() => String)
restoreAccessToken(
@CurrentUser() currentUser: any, //
) {
return this.authService.getAccessToken({ user: currentUser });
}
}
auth.service.ts
import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthService {
constructor(
private readonly jwtService: JwtService, //
) {}
setRefreshToken({ user, res }) {
const refreshToken = this.jwtService.sign(
{ email: user.email, sub: user.id },
{ secret: 'myRefreshKey', expiresIn: '2w' },
);
// 개발 환경
res.setHeader('Set-Cookie', `refreshToken=${refreshToken}`);
// 배포 환경
// res.setHeader('Access-Control-Allow-Origin', 'https://myfrontsite.com')
// res.setHeader(
// 'Set-Cookie',
// `refreshToken=${refreshToken}; path=/; domain=.mybacksite.com; SameSite=None; Secure; httpOnly;`
// )
}
getAccessToken({ user }) {
return this.jwtService.sign(
{ email: user.email, sub: user.id },
{ secret: 'myAccessKey', expiresIn: '30s' },
);
}
}
jwt-refresh.strategy.ts
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-jwt';
export class JwtRefreshStrategy extends PassportStrategy(Strategy, 'refresh') {
constructor() {
super({
jwtFromRequest: (req) => {
const cookie = req.headers.cookie;
const refreshToken = cookie.replace('refreshToken=', '');
return refreshToken;
},
secretOrKey: 'myRefreshKey',
});
}
validate(payload) {
console.log(payload); // { email: c@c.com, sub: qkwefuasdij-012093sd }
return {
email: payload.email,
id: payload.sub,
};
}
}
gql-auth-guard.ts
import { ExecutionContext } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { AuthGuard } from '@nestjs/passport';
export class GqlAuthAccessGuard extends AuthGuard('access') {
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req;
}
}
export class GqlAuthRefreshGuard extends AuthGuard('refresh') {
getRequest(context: ExecutionContext) {
const ctx = GqlExecutionContext.create(context);
return ctx.getContext().req;
}
}
app.module.ts
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AuthModule } from './apis/auth/auth.module';
import { BoardModule } from './apis/boards/boards.module';
import { ProductModule } from './apis/products/product.module';
import { ProductCategoryModule } from './apis/productCategory/productCategory.module';
import { UserModule } from './apis/users/user.module';
// import { AppController } from './app.controller';
// import { AppService } from './app.service';
@Module({
imports: [
AuthModule,
BoardModule,
ProductModule,
ProductCategoryModule,
UserModule,
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: 'src/commons/graphql/schema.gql',
context: ({ req, res }) => ({ req, res }),
}),
TypeOrmModule.forRoot({
type: 'mysql', // 데이터 베이스 타입
host: 'localhost', // local 환경으로 진행
port: 3306, // mysql은 기본 port는 3306
username: 'root', // mysql은 기본 user는 root로 지정
password: 'bada332@', // 본인의 mysql password
database: 'myproject03', // 연결할 데이터 베이스명
entities: [__dirname + '/apis/**/*.entity.*'], // 데이터 베이스와 연결할 entity
synchronize: true, // entity 테이블을 데이터베이스와 동기화할 것인지
logging: true, // 콘솔 창에 log를 표시할 것인지
}),
],
// controllers: [AppController],
// providers: [AppService],
})
export class AppModule {}
답변 1
1
안녕하세요. yjin0034님
문의 주신 내용을 확인했을 시, 코드에는 큰 문제가 없는 것으로 파악됩니다.
브라우저에 쿠키값이 두개가 들어오게 되어 만들어진 로직으로는 인가 부분이 제대로 실행되지 않은 것으로 파악되니 이 부분 참고하시어 브라우저 쿠키 삭제 후 다시 시도해 보시길 바랍니다. 감사합니다.
말씀대로 쿠키 삭제 후 재시도 해보니 정상적으로 되네요.
답변 감사합니다!