I'm very new to SimpleSAMLphp and I'm diving in headfirst with the help of productivity AI answers so please bear with me. (Currently in a very tight schedule project so I had to use AI. I know it isn't really reliable, and yes I have been double-checking the info it gives me.)
I'm using 24Slides/laravel-saml2 together with Laravel, and not aacotroneo/laravel-saml2 as suggested on its README. There were some errors here and there that were solved later on, but I keep bumping into this error when accessing https://localhost:8443/login
.
Before getting the error, I do see the 'Enter username and password' page, which tells me that it's working, but after I enter my details and the app processes it, it always leads me to a 500 Server Error
page with The response was received at https://localhost/saml2/acs instead of https://localhost:8443/saml2/acs
at the header.
config/saml2.php
:
<?php
return [
'default' => 'default',
'tenantModel' => \Slides\Saml2\Models\Tenant::class,
'useRoutes' => true,
'routesPrefix' => 'saml2',
'routesMiddleware' => ['web'],
'retrieveParametersFromServer' => false,
'loginRoute' => 'login',
'logoutRoute' => 'logout',
'errorRoute' => env('SAML2_ERROR_URL'),
'strict' => true,
'debug' => env('SAML2_DEBUG', env('APP_DEBUG', false)),
'proxyVars' => true,
'sp' => [
'NameIDFormat' => 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent',
'entityId' => 'https://localhost:8443/saml2/metadata',
'assertionConsumerService' => [
'url' => 'https://localhost:8443/saml2/acs',
],
'singleLogoutService' => [
'url' => 'https://localhost:8443/saml2/sls'
],
'x509cert' => file_get_contents(base_path('storage/saml/sp-cert.crt')),
'privateKey' => file_get_contents(base_path('storage/saml/sp-key.pem')),
],
'load_migrations' => true,
'idp' => [
'entityId' => 'https://localhost:8443/simplesaml/saml2/idp/metadata.php',
'singleSignOnService' => [
'url' => 'https://localhost:8443/simplesaml/saml2/idp/SSOService.php',
],
'singleLogoutService' => [
'url' => 'https://localhost:8443/simplesaml/saml2/idp/SingleLogoutService.php',
],
'x509cert' => env('SAML2_IDP_X509', ''),
],
];
simplesaml/metadata/saml20-sp-remote.php
:
$metadata['https://localhost:8443/saml2/metadata'] = [
'AssertionConsumerService' => [
[
'Location' => 'https://localhost:8443/saml2/acs',
'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST',
],
],
'SingleLogoutService' => [
[
'Location' => 'https://localhost:8443/module.php/saml2/logout',
'Binding' => 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect',
],
],
];
Additional edit:
.env
:
APP_NAME=Laravel
APP_ENV=local
APP_FORCE_HTTPS=true
APP_KEY=
APP_DEBUG=true
APP_TIMEZONE=
APP_URL=https://localhost:8443
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=file
SAML2_DEBUG=true
SAML2_SP_ENTITYID=https://localhost:8443/saml2/metadata
SAML2_SP_ACS=https://localhost:8443/saml2/acs
SAML2_IDP_ENTITYID=https://localhost:8443/metadata
SAML2_IDP_SSO=https://localhost:8443/sso
SAML2_IDP_SLO=https://localhost:8443/slo
SAML2_IDP_X509=
PHP_CLI_SERVER_WORKERS=4
BCRYPT_ROUNDS=12
LOG_CHANNEL=stack
LOG_STACK=single
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug
DB_CONNECTION=pgsql
DB_HOST=postgres
DB_PORT=5432
DB_DATABASE=
DB_USERNAME=
DB_PASSWORD=
SESSION_DRIVER=database
SESSION_COOKIE=laravel_session
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null
BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
MEMCACHED_HOST=127.0.0.1
VITE_APP_NAME="${APP_NAME}"
I did try searching for similar issues like mine but the closest I got was this issue.
Using Docker, NGINX, Laravel, and SimpleSAML.
Please let me know which files I need to place here or anything else. Thank you so much.
I think I got the answer. (I asked for help from a co-worker in the office.)
SOLUTION: on app/Http/Middleware
, create a file EnableSamlProxyVars.php
and add this to the file:
namespace App\Http\Middleware;
use Closure;
use OneLogin\Saml2\Utils;
class EnableSamlProxyVars {
public function handle($request, Closure $next) {
Utils::setProxyVars(true);
return $next($request);
}
}
then adding it to bootstrap/app.php
:
$middleware->use([
App\Http\Middleware\TrustProxies::class,
\App\Http\Middleware\EnableSamlProxyVars::class,
\Illuminate\Http\Middleware\HandleCors::class
]);
additional info: TrustProxies.php includes:
namespace App\Http\Middleware;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
use Illuminate\Http\Middleware\TrustProxies as Middleware;
use Closure;
class TrustProxies extends Middleware {
protected $proxies = '*';
protected $headers =
Request::HEADER_X_FORWARDED_FOR |
Request::HEADER_X_FORWARDED_HOST |
Request::HEADER_X_FORWARDED_PORT |
Request::HEADER_X_FORWARDED_PROTO;
}
============================================
(Apologies if this part is too detailed, as I want to potentially help those who may come across this error in the future, but please do let me know how to simplify this.)
Explanation:
TL;DR: OneLogin (based on by 24slides/laravel-saml2) sets proxyVars
to false
on its Utils.php file (vendor/onelogin/php-saml/src/Saml2/Utils.php
).
On my routes/web.php
,
Route::prefix('saml2')->group(function () {
Route::get('login', [Saml2Controller::class, 'login']);
Route::post('acs', [Saml2Controller::class, 'acs'])->withoutMiddleware([\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class]);
Route::get('logout', [Saml2Controller::class, 'logout']);
Route::get('sls', [Saml2Controller::class, 'sls']);
Route::get('metadata', [Saml2Controller::class, 'metadata']);
});
It took a lot of time checking the functions and seeing where the 443
port was coming from, but it led us to vendor/onelogin/php-saml/src/Saml2/Response.php
: (particularly on this section)
$urlComparisonLength = $security['destinationStrictlyMatches'] ? strlen($destination) : strlen($currentURL);
if (strncmp($destination, $currentURL, $urlComparisonLength) !== 0) {
$currentURLNoRouted = Utils::getSelfURLNoQuery();
$urlComparisonLength = $security['destinationStrictlyMatches'] ? strlen($destination) : strlen($currentURLNoRouted);
if (strncmp($destination, $currentURLNoRouted, $urlComparisonLength) !== 0) {
throw new ValidationError(
"The response was received at $currentURL instead of $destination",
ValidationError::WRONG_DESTINATION
);
}
}
Utils::getSelfURLNoQuery
led us to getSelfURLhost()
, which then led us to getSelfPort()
.
if (self::$_port) {
$portnumber = self::$_port;
//var_dump($_port . 'one');
} else if (self::getProxyVars() && isset($_SERVER["HTTP_X_FORWARDED_PORT"])) {
$portnumber = $_SERVER["HTTP_X_FORWARDED_PORT"];
//var_dump($portnumber . 'two');
} else if (isset($_SERVER["SERVER_PORT"])) {
$portnumber = $_SERVER["SERVER_PORT"];
//var_dump($portnumber . 'three');
}
(var_dump
helped locating which part was the system going through.)
Adding this to any of our Laravel files (added this to routes/web.php
):
dd([
'server' => $_SERVER,
'headers' => getallheaders(),
'scheme' => request()->getScheme(),
'port' => request()->getPort(),
'url' => url('/'),
]);
helped confirm that OneLogin was getting $_SERVER["SERVER_PORT"]
which was still equal to 443
because of this variable in Utils.php:
class Utils
{
const RESPONSE_SIGNATURE_XPATH = "/samlp:Response/ds:Signature";
const ASSERTION_SIGNATURE_XPATH = "/samlp:Response/saml:Assertion/ds:Signature";
/**
* @var bool Control if the `Forwarded-For-*` headers are used
*/
private static $_proxyVars = false;
The middleware solution was recommended by AI, as it looked like OneLogin didn't get the 'proxyVars' => true,
in my config/saml2.php
.
The solution mostly focused on changing proxyVars
to true
so that the system would go through the else if (self::getProxyVars() && isset($_SERVER["HTTP_X_FORWARDED_PORT"]))
part.
Refactored docker/nginx/default.conf
:
server {
listen 443 ssl;
server_name localhost;
ssl_certificate /etc/nginx/ssl/localhost.pem;
ssl_certificate_key /etc/nginx/ssl/localhost-key.pem;
root /app/public;
index index.php index.html;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port 8443;
# Laravel app routes
location / {
try_files $uri $uri/ /index.php?$query_string;
proxy_set_header Host $host;
proxy_pass http://app:80;
proxy_set_header X-Forwarded-Port 8443;
}
# PHP handling
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass app:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param HTTPS on;
fastcgi_param HTTP_X_FORWARDED_PROTO $scheme;
fastcgi_param HTTP_X_FORWARDED_PORT 8443;
}
# SimpleSAMLphp frontend
location ^~ /simplesaml/ {
alias /app/simplesaml/public/;
index index.php;
location ~ ^/simplesaml/(.*\.php)(/.*)?$ {
alias /app/simplesaml/public/;
include fastcgi_params;
fastcgi_pass app:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /app/simplesaml/public/$1;
fastcgi_param PATH_INFO $2;
fastcgi_param HTTP_X_FORWARDED_PORT 8443;
}
}
}