Laravel authenticated user ID in NGINX access log

Add the authenticated user ID to the NGINX access log for easier debugging your Laravel application.

Alex Bouma

Alex Bouma

Founder Chief Tools

For debugging and/or easy searching it's handy to have the user ID in the NGINX access logs so you can quickly find logs for a specific user.

There are 2 parts to achieving this:

  1. Add the user ID as a response header to our Laravel app using a middleware
  2. Add the user ID as part of the NGINX log format and use that log format for the access logs

Adding the user ID as a response header

It makes the most sense to use a middleware for this purpose and we can make it really simple thanks to Laravel and PHP 8 syntax.

Create the app/Http/Middleware/AppendUserIdToResponse.php file:

1<?php
2 
3namespace App\Http\Middleware;
4 
5use Closure;
6use Illuminate\Http\Request;
7use Symfony\Component\HttpFoundation\Response;
8 
9class AppendUserIdToResponse
10{
11 public function handle(Request $request, Closure $next): Response
12 {
13 /** @var \Symfony\Component\HttpFoundation\Response $response */
14 $response = $next($request);
15 
16 $response->headers->set('x-user', $request->user()?->id ?? '-');
17 
18 return $response;
19 }
20}

Note

I'm taking $request->user()?->id here but nothing is stopping you from using $request->user()?->username if that makes more sense for you.

We always add a header but if the user is not logged in we default to a - which NGINX defaults to when the header is not set at all.

Next we are going to register this new middleware in the app/Http/Kernel.php in the $middleware array:

1 /**
2 * The application's global HTTP middleware stack.
3 *
4 * These middleware are run during every request to your application.
5 *
6 * @var array
7 */
8 protected $middleware = [
9 // \App\Http\Middleware\TrustHosts::class,
10 \App\Http\Middleware\TrustProxies::class,
11 \Fruitcake\Cors\HandleCors::class,
12 \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
13 \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
14 \App\Http\Middleware\TrimStrings::class,
15 \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
16+ \App\Http\Middleware\AppendUserIdToResponse::class,
17 ];

Note

We are adding it to the global middleware stack to make sure it runs for every request instead of needing to register it per route.

This is it for the Laravel side (don't forget to deploy)!

Adding the user ID to the NGINX log format

This part is a bit more "custom" and depends on how you have set up your NGINX and log formats, but let's give it a shot!

Custom log formats can be defined in /etc/nginx/nginx.conf (within the http {} block) and could look something like this:

1log_format main_ext '$remote_addr - $remote_user [$time_local] "$request" '
2 '$status $body_bytes_sent "$http_referer" '
3 '"$http_user_agent" "$http_x_forwarded_for" '
4 '"$host" sn="$server_name" '
5 'rt=$request_time '
6 'ua="$upstream_addr" us="$upstream_status" '
7 'ut="$upstream_response_time" ul="$upstream_response_length" '
8 'cs=$upstream_cache_status '
9 'uid="$upstream_http_x_smls_user"';

Note

This log_format is the log format required by NGINX Amplify and used as an example.

To add our user ID to this we would do this:

1log_format main_ext '$remote_addr - $remote_user [$time_local] "$request" '
2 '$status $body_bytes_sent "$http_referer" '
3 '"$http_user_agent" "$http_x_forwarded_for" '
4 '"$host" sn="$server_name" '
5 'rt=$request_time '
6 'ua="$upstream_addr" us="$upstream_status" '
7 'ut="$upstream_response_time" ul="$upstream_response_length" '
8- 'cs=$upstream_cache_status';
9+ 'cs=$upstream_cache_status '
10+ 'uid="$upstream_http_x_user"';

Notice the added uid="$upstream_http_x_user" at the end, this will render as uid="1" if user 1 is logged in or uid="-" if no user is logged in.

It's possible you do not have a custom log_format then your are using the default combined format, add this log format which is combined with the uid field added:

1log_format combined_with_user_id '$remote_addr - $remote_user [$time_local] '
2 '"$request" $status $body_bytes_sent '
3 '"$http_referer" "$http_user_agent" '
4 'uid="$upstream_http_x_user"';

After this we need to find the access_log entries in our vhosts and make sure the correct log format is used:

1server {
2 ...
3 access_log /var/log/nginx/access.log combined_with_user_id; # or whatever you named your log format
4 error_log /var/log/nginx/error.log warn;
5 ...
6}

There is one last optional step, and that is to hide this header from the response. It should not be a security issue but there is no reason to have that header in the response so we are going to hide it:

Locate the fastcgi_pass section in your vhost config and add fastcgi_hide_header x-user;, could look something like this:

1location = /index.php {
2 include /etc/nginx/fastcgi_params;
3 
4 fastcgi_param HTTPS on;
5 fastcgi_param HTTP_SCHEME https;
6 fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
7 
8 fastcgi_pass php80;
9 fastcgi_index index.php;
10 fastcgi_hide_header x-user;
11 fastcgi_hide_header x-powered-by;
12 fastcgi_split_path_info ^(.+\.php)(.*)$;
13}

Wrapping up

After you've made these changes NGINX should add uid="1" if user 1 is logged in or uid="-" if no user is logged in to the access log entries.

One caveat is that this only works for "dynamic" requests, requests that are handled by Laravel/PHP, so static files (like CSS / JS) will always have uid="-".

Need help?

We are happy to help you with any questions you might have.

  Documentation   Roadmap   Report a bug   Get in touch