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

如何在Serverless GraphQL Lambda应用中抛出认证错误

How to Properly Throw Authentication Errors in GraphQL-Yoga Serverless Lambda

Let's break down what's going wrong here and fix it step by step. The core issue is that your error isn't being serialized into a valid GraphQL error response, which is why your client is getting that "Unexpected token I in JSON..." parsing error. Here's how to resolve it:

1. Define a Custom Authentication Error Type

First, create a dedicated error class to distinguish authentication-related issues from other server errors. This makes consistent error handling much easier later:

class AuthenticationError extends Error {
  constructor(message) {
    super(message);
    this.name = 'AuthenticationError';
    this.statusCode = 401;
  }
}

2. Refine Your Authentication Logic

Update your authenticate function to handle all three of your required cases (no token, valid token, invalid/expired token) clearly, and only throw the custom error when needed:

async function authenticate(authHeader) {
  // Case 1: No token provided - return empty object as required
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return {};
  }

  const token = authHeader.split(' ')[1];
  
  try {
    // Verify JWT and fetch user data (adjust this to match your actual JWT verification flow)
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    const user = await mainDb.user.findUnique({ where: { id: decoded.userId } });
    
    // Return user or empty object if user isn't found in the database
    return user || {};
  } catch (e) {
    // Case 2: Token is expired or invalid - throw our custom authentication error
    if (['TokenExpiredError', 'JsonWebTokenError'].includes(e.name)) {
      throw new AuthenticationError('Invalid or expired authentication token');
    }
    // Re-throw other unexpected errors for general handling
    throw e;
  }
}

3. Simplify the GraphQL Server Context

Don't catch and re-throw errors directly in the context promise chain. graphql-yoga is designed to handle promise rejections automatically, so let the error bubble up naturally. Rewrite your context as an async function for clarity:

const lambda = new GraphQLServerLambda({
  typeDefs,
  context: async ({ event }) => {
    const user = await authenticate(event.headers.Authorization);
    return { db: mainDb, user };
  },
  resolvers: {
    Query: { /* Your query resolvers here */ },
    Mutation: { /* Your mutation resolvers here */ }
  }
});

4. Fix the Lambda Handler to Serialize Errors Correctly

The biggest issue in your original code is that the Lambda handler wasn't properly formatting errors into valid JSON responses. Here's the revised handler that ensures both successful and error responses are correctly structured:

exports.server = async (event, context, callback) => {
  try {
    // Run the GraphQL handler and get the result
    const graphqlResult = await lambda.graphqlHandler(event, context);
    
    // Return a properly formatted success response
    callback(null, {
      statusCode: 200,
      headers: {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*' // Adjust CORS settings to match your application needs
      },
      body: JSON.stringify(graphqlResult)
    });
  } catch (e) {
    // Format errors to comply with the GraphQL specification
    let errors = [];
    
    if (e instanceof AuthenticationError) {
      errors.push({
        message: e.message,
        extensions: {
          code: 'UNAUTHENTICATED',
          statusCode: e.statusCode
        }
      });
    } else {
      // For production environments, avoid exposing raw error details to clients
      errors.push({
        message: 'Internal server error',
        extensions: {
          code: 'INTERNAL_SERVER_ERROR',
          statusCode: 500
        }
      });
      // Log the full error details for debugging purposes
      console.error('Server error:', e);
    }
    
    // Return the formatted error response as valid JSON
    callback(null, {
      statusCode: errors[0].extensions.statusCode,
      headers: {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*'
      },
      body: JSON.stringify({ errors })
    });
  }
};

Why This Works

  • Custom Error Class: Makes it easy to identify authentication errors and format them consistently for the client.
  • Clean Authentication Flow: Clearly separates your three required cases, ensuring no token returns an empty object as specified.
  • Valid JSON Responses: The Lambda handler now explicitly converts both success and error results into valid JSON, eliminating the parsing error your client was seeing.
  • GraphQL Spec Compliance: Errors are returned in the standard GraphQL errors array format, so your client can parse and handle them correctly.

Bonus: Enforce Auth in Resolvers

Now that your context includes a user object (or empty), you can add checks in your resolvers to restrict access to authenticated users:

Query: {
  protectedData: (parent, args, { user }) => {
    if (Object.keys(user).length === 0) {
      throw new AuthenticationError('You must be logged in to access this data');
    }
    // Fetch and return your protected data here
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 06:41:50