Photo by Radek Grzybowski on Unsplash
Laravel authenticated user ID in NGINX access log
Let's add the authenticated user ID to the NGINX access log for easier debugging.
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:
- Add the user ID as a response header to our Laravel app using a middleware
- 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:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class AppendUserIdToResponse
{
public function handle(Request $request, Closure $next): Response
{
/** @var \Symfony\Component\HttpFoundation\Response $response */
$response = $next($request);
$response->headers->set('x-user', $request->user()?->id ?? '-');
return $response;
}
}
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:
/**
* The application's global HTTP middleware stack.
*
* These middleware are run during every request to your application.
*
* @var array
*/
protected $middleware = [
// \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Fruitcake\Cors\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
+ \App\Http\Middleware\AppendUserIdToResponse::class,
];
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:
log_format main_ext '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'"$host" sn="$server_name" '
'rt=$request_time '
'ua="$upstream_addr" us="$upstream_status" '
'ut="$upstream_response_time" ul="$upstream_response_length" '
'cs=$upstream_cache_status '
'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:
log_format main_ext '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'"$host" sn="$server_name" '
'rt=$request_time '
'ua="$upstream_addr" us="$upstream_status" '
'ut="$upstream_response_time" ul="$upstream_response_length" '
- 'cs=$upstream_cache_status';
+ 'cs=$upstream_cache_status '
+ '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:
log_format combined_with_user_id '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'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:
server {
...
access_log /var/log/nginx/access.log combined_with_user_id; # or whatever you named your log format
error_log /var/log/nginx/error.log warn;
...
}
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:
location = /index.php {
include /etc/nginx/fastcgi_params;
fastcgi_param HTTPS on;
fastcgi_param HTTP_SCHEME https;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_pass php80;
fastcgi_index index.php;
fastcgi_hide_header x-user;
fastcgi_hide_header x-powered-by;
fastcgi_split_path_info ^(.+\.php)(.*)$;
}
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="-"
.