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

No one assigned

    Labels

    confirmedThe scope is clear, ready for implementationdocumentationImprovements or additions to documentation

    Type

    No type

    Projects

    Status

    Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions