如何在Serverless GraphQL 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
errorsarray 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

