Skip to content

Sentry NodeJS setUser per request #13205

Closed
Closed
@sbriceland

Description

@sbriceland

Is there an existing issue for this?

How do you use Sentry?

Sentry Saas (sentry.io)

Which SDK are you using?

@sentry/node

SDK Version

8.20.0

Framework Version

@sentry/node

Link to Sentry event

No response

Reproduction Example/SDK Setup

How to set User per request in Node?

From Scopes Documentation:

For instance, a web server might handle multiple requests at the same time, and each request may have different scope data to apply to its events.

The isolation scope is used to isolate events from each other. For example, each request in a web server might get its own isolation scope, so that events from one request don't interfere with events from another request. In most cases, you'll want to put data that should be applied to your events on the isolation scope - which is also why all Sentry.setXXX methods, like Sentry.setTag(), will write data onto the currently active isolation scope. A classic example for data that belongs on the isolation scope is a user - each request may have a different user, so you want to make sure that the user is set on the isolation scope

... The documentation matches exactly the behavior needed. However, it simply does not work.

  • make an authenticated request, lookup user details, call setUser
  • make an unauthenticated request, do not call to setUser for this request (scope) because we don't have one, and throw an error manually

Problems

  • The manually thrown error will report the User from the previous authenticated request.
  • The same problem exists for spans.
  • This is intentionally simple, but we've seen many errors report the wrong user

This example using Express should demonstrate...

import * as Sentry from '@sentry/node';
import bodyParser from 'body-parser';
import express, { Application, NextFunction, Request, Response, Router } from 'express';

Sentry.init({
    dsn: undefined, // not needed for local debugging to reveal the issue
    beforeSend: (event, hint, ...args) => {
        const { type, contexts, exception, extra, tags, message, user, request } = event;
        console.dir(
            {
                whoami: 'sentry:beforeSend',
                event: { type, contexts, exception, extra, tags, message, user, request },
                hint,
                args
            },
            { depth: null }
        );
        return event;
    },
    skipOpenTelemetrySetup: true
});

const app: Application = express();

const router = Router();

router.use(bodyParser.urlencoded({ extended: true, limit: '500kb' }));
router.use(bodyParser.json({ limit: '500kb' }));

const Users: { id: string; email: string; name: string }[] = [
    { id: '1', email: '[email protected]', name: 'foo example' },
    { id: '2', email: '[email protected]', name: 'foo example2' },
    { id: '3', email: '[email protected]', name: 'foo example3' },
    { id: '4', email: '[email protected]', name: 'foo example4' }
];

router.use('/users', function (req, res, next) {
    try {
        const authUser = Users.find((u) => u.id === req.headers['authorization']);
        if (authUser) {
            Sentry.setTag('Authenticated', true);
            Sentry.setUser(authUser);
            res.json(Users);
        } else {
            throw new Error('Authentication Error');
        }
    } catch (err) {
        next(err);
    }
});

app.use('/api', router);

app.use(function (err: Error, req: Request, res: Response, next: NextFunction) {
    const { method, originalUrl, params, query, body } = req;
    const { statusCode, locals } = res;

    Sentry.withScope((scope) => {
        scope.setExtras({
            request: { method, originalUrl, params, query, body },
            response: { statusCode, locals }
        });
        const eventId = Sentry.captureException(err);
        (res as { sentry?: string }).sentry = eventId;
    });

    next(err);
});

// Or just use this, which is identical to above, without `extras`
// Sentry.setupExpressErrorHandler(app);

const PORT = process.env.PORT || 3000
app.listen(PORT, () => {
    console.log(`API running @ http://localhost:${PORT}`);
});

Steps to Reproduce

Send the following requests:

# make an "authenticated" request
curl --header "authorization: 1" --header "content-type: application/json" http://localhost:3000/api/users

# subsequently make an unauthenticated request
curl --header "authorization: 798798798798" --header "content-type: application/json" http://localhost:3000/api/users

Expected Result

Logs show different user context. Also, this is only a very simple issue. We've seen many users mis reported in our Prod environment. So it would seem that Scopes are not isolated to a request.

Actual Result

same user for requests with different auth

Metadata

Metadata

Assignees

Labels

Package: nodeIssues related to the Sentry Node SDK

Type

Projects

Status

Waiting for: Product Owner

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions