You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

AuthGuard返回true但HTTP返回401 Unauthorized的NestJS认证问题

NestJS会话认证异常:AuthGuard验证通过但接口返回401

我使用NestJS、Express Sessions和Passport实现会话认证(非JWT令牌认证),尽管自定义AuthGuard的日志明确输出User authenticated: true,但调用标注了@UseGuards(AuthGuard)的API接口时,仍会返回{"statusCode": 401, "message": "Unauthorized"}。

自定义AuthGuard代码

@Injectable()
export class AuthGuard implements CanActivate {
  private readonly logger = new Logger(AuthGuard.name);

  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    const isAuthenticated = request.isAuthenticated();
    this.logger.log(`User authenticated: ${isAuthenticated}`);
    this.logger.log(`Request headers: ${JSON.stringify(request.headers)}`);
    this.logger.log(`Request user: ${JSON.stringify(request.user)}`);
    this.logger.log(`Session: ${JSON.stringify(request.session)}`);
    return isAuthenticated;
  }
}

控制台输出示例:

LOG [AuthGuard] User authenticated: true

LocalAuthGuard代码

@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {
  async canActivate(context: ExecutionContext): Promise<boolean> {
    const result = (await super.canActivate(context)) as boolean;
    const request = context.switchToHttp().getRequest();
    await super.logIn(request);
    return result;
  }
}

AuthService代码

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

  async validateUser(username: string, password: string): Promise<User> {
    const user: User = await this.userService.validateUser(username, password);
    if (!user) {
      throw new UnauthorizedException('Invalid credentials');
    }
    return user;
  }
}

LocalStrategy代码

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

  async validate(username: string, password: string): Promise<User> {
    return this.authService.validateUser(username, password);
  }
}

AuthController代码

@Controller('auth')
export class AuthController {
  /**
   * This is a POST request to /auth/login that logs in a user
   * and returns the user object
   *
   * @param {e.Request} req - the request object
   * @returns {Promise<Express.User>} - the user object
   */
  @Throttle({ default: { limit: 5, ttl: 300000 } }) // 5 requests per 5 minutes
  @UseGuards(LocalAuthGuard)
  @Post('login')
  async login(@Request() req: ExpressRequest): Promise<Express.User> {
    // Passport automatically attaches user to the request object
    return req.user;
  }

  /**
   * This is a POST request to /auth/logout that logs out a user
   * and returns a message
   *
   * @param {e.Request} req - the request object
   * @param {e.Response} res - the response object
   * @param {e.NextFunction} next - the next function
   * @returns {Promise<{message: string}>} - a message
   */
  @UseGuards(AuthGuard)
  @Throttle({ default: { limit: 5, ttl: 60000 } }) // 5 requests per minute
  @Post('logout')
  async logout(
    @Request() req: ExpressRequest,
    @Res() res: Response,
    @Next() next: NextFunction,
  ): Promise<{ message: string }> {
    req.logout(function (err) {
      if (err) {
        return next(err);
      }

      // Clear the session cookie
      req.session = null;

      res.redirect('/');
    });
    return { message: 'Logged out successfully' };
  }

  /**
   * This is a GET request to /auth/session that returns the session
   * and the session id.
   *
   * @param {e.Request} req - the request object
   * @returns {Promise<{session: session.Session, sessionId: string}>} - the session and the session id
   */
  @Get('session')
  @UseGuards(AuthGuard)
  async getAuthSession(
    @Request() req: ExpressRequest,
  ): Promise<{ session: Session; sessionId: string }> {
    const session: Session = req.session;
    return {
      session: session,
      sessionId: session.id,
    };
  }
}

SessionSerializer代码

@Injectable()
export class SessionSerializer extends PassportSerializer {
  constructor(private readonly usersService: UsersService) {
    super();
  }

  serializeUser(user: User, done: (err: Error, user: User) => void) {
    done(null, user);
  }

  async deserializeUser(
    sessionUser: User,
    done: (err: Error, user: User) => void,
  ) {
    const user = await this.usersService.findUserById(sessionUser.id);
    return user ? done(null, user) : done(null, null);
  }
}

Main.ts代码

require('mysql2');

async function bootstrap(): Promise<void> {
  const app = await NestFactory.create(AppModule);

  // CORS
  ...

  const mySqlStore = require('express-mysql-session')(session);

  const APP_PORT: string = process.env.APP_PORT || '3000';
  const IN_PROD: boolean = process.env.NODE_ENV === 'production';
  const TEN_MINUTES: number = 1000 * 60 * 10;

  const options = {
    connectionLimit: 10,
    connectTimeout: 10000,
    host: process.env.HOST,
    port: parseInt(process.env.PORT), // db port
    user: process.env.USER,
    password: process.env.PASSWORD,
    database: process.env.DATABASE,
    createDatabaseTable: true,
  };

  const pool: mysql.Pool = mysql.createPool(options);
  const sessionStore = new mySqlStore(options, pool);

  // Set up the session middleware
  app.use(
    session({
      name: process.env.SESS_NAME,
      genid: function () {
        return uuidV4();
      },
      secret: process.env.SESS_SECRET,
      resave: false,
      saveUninitialized: false,
      store: sessionStore,
      cookie: {
        httpOnly: true,
        secure: IN_PROD,
        maxAge: TEN_MINUTES,
        sameSite: 'lax', // 'lax' for local testing
      },
      rolling: true, // Reset the maxAge on every request
    }),
  );

  // Initialize and set up the Passport session middleware
  app.use(passport.initialize());
  app.use(passport.session());

  await app.listen(APP_PORT);
}

bootstrap().catch((err) => console.error(err));

关键异常现象

  • 即使接口返回401未授权,已登录用户的实际操作仍会执行(例如点赞帖子,错误响应返回但点赞记录已生成)
  • 问题近期才出现,疑似版本更新引入的bug,但未找到同类讨论案例

内容的提问来源于stack exchange,提问作者CairoMisr

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 10:00:55