Skip to content

Docs: event-handler middleware example uses unsafe module-level state for authentication #5103

@HannesOberreiter

Description

@HannesOberreiter

What were you searching in the docs?

Event Handler: the documentation and snippet at docs.aws.amazon.com (and also in examples/snippets/event-handler/http/advanced_mw_custom_middleware.ts) shows an authentication middleware that stores authentication state in a module-level variable named store:

const store: { userId: string; roles: string[] } = { userId: '', roles: [] };

This store is then used in both the middleware and route handler.

  • Module-level variables in Lambda persist across warm invocations. If one request changes store, the next request may see previous values, causing data leakage or, worse, security vulnerabilities.
  • Anti-pattern for per-request data. Documentation examples should show best practices, but this encourages using global mutable state for request-scoped data—a serious Lambda anti-pattern.
  • Potential data leak or race condition. In rare cases (provisioned concurrency, etc.), requests can race or leak user data.

Is this related to an existing documentation section?

https://docs.aws.amazon.com/powertools/typescript/latest/features/event-handler/http/#custom-middleware

How can we improve?

Update the documentation and code examples to demonstrate safely attaching request-scoped authentication data to reqCtx.

Got a suggestion in mind?

Instead, the middleware should attach authentication state directly to the reqCtx (request context), which is scoped to each invocation. For example:

const verifyToken = (options: { jwtSecret: string }): Middleware => {
  return async ({ reqCtx, next }) => {
    const auth = reqCtx.req.headers.get('Authorization');
    if (!auth || !auth.startsWith('Bearer '))
      throw new UnauthorizedError('Missing or invalid Authorization header');

    const token = auth.slice(7);
    try {
      const payload = jwt.verify(token, options.jwtSecret);
      // Attach to request context, not a module-level variable
      reqCtx.userId = payload.sub;
      reqCtx.roles = payload.roles;
    } catch (error) {
      logger.error('Token verification failed', { error });
      throw new UnauthorizedError('Invalid token');
    }

    await next();
  };
};

app.post('/todos', async (reqCtx) => {
  const { userId } = reqCtx;
  const todos = await getUserTodos(userId);
  return { todos };
});

Acknowledgment

  • I understand the final update might be different from my proposed suggestion, or refused.

Metadata

Metadata

Assignees

Labels

completedThis item is complete and has been merged/shippeddocumentationImprovements or additions to documentation

Type

No type

Projects

Status

Shipped

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions