tuupola / slim-basic-auth

PSR-7 and PSR-15 HTTP Basic Authentication Middleware
MIT License
440 stars 66 forks source link

Basic case-insensitive and PHP_AUTH_USER #33

Open Cyclenerd opened 7 years ago

Cyclenerd commented 7 years ago

Hi,

I have the following code:

$app->add(new \Slim\Middleware\HttpBasicAuthentication([
    "path" => ["/auth", "/user", "/search"],
    "realm" => "Protected",
    "authenticator" => new PdoAuthenticator([
        "pdo" => $authenticator_pdo,
        "table" => "users",
        "user" => "username",
        "hash" => "password_hash"
    ]),
    "callback" => function ($request, $response, $arguments) {
        print_r($arguments);
    },
    "error" => function ($request, $response, $arguments) {
        return $response->withJson(array('error' => 'AUTHENTICATION_FAILED'), 403);
    }
]));

// Check HTTP Basic Authentication
$app->get('/auth', function ($request, $response, $args) {
    $auth_username = $_SERVER['PHP_AUTH_USER'];
    // Return
    return $response->withJson(array(
        'username' => $auth_username,
        'status' => 'OK'
    ), 200);
});

If I pass in the header "Authorization: Basic" (upper case B) the authentication is successful and PHP_AUTH_USER is set:

curl 'http://localhost:8080/auth' -H 'Authorization: Basic bmlsczp0ZXN0MTIzNA=='
Array
(
    [user] => nils
    [password] => test1234
)
{"username":"nils","status":"OK"}

If I pass in the header "Authorization: basic" (lowercase letter b) the authentication is successful and PHP_AUTH_USER is not set.

curl 'http://localhost:8080/auth' -H 'Authorization: basic bmlsczp0ZXN0MTIzNA=='
Array
(
    [user] => nils
    [password] => test1234
)
{"username":null,"status":"OK"}

When I remove the case-insensitive (/i) Regular Expression in HttpBasicAuthentication.php then the authentication with basic (lowercase letter b) fails:

curl 'http://localhost:8080/auth' -H 'Authorization: basic bmlsczp0ZXN0MTIzNA=='
{"error":"AUTHENTICATION_FAILED"}

That would be better in my case. I am briefly overflown the RFCs. Basic is always written with (upper case B).

Best regards Nils

tuupola commented 7 years ago

According to RFC2617 the authentication scheme is case-insensitive.

HTTP provides a simple challenge-response authentication mechanism that MAY be used by a server to challenge a client request and by a client to provide authentication information. It uses an extensible, case-insensitive token to identify the authentication scheme, followed by a comma-separated list of attribute-value pairs which carry the parameters necessary for achieving authentication via that scheme.

PHP itself is quite picky about format of of headers though. What is the problem current behaviour causes you?

Cyclenerd commented 7 years ago

Thanks for the super fast response and the clarification of the RFC.

My problem is that I trust the authentication and expect a PHP_AUTH_USER. I double check this now:

$check_auth_user = function ($request, $response, $next) {
    if (isset($_SERVER['PHP_AUTH_USER'])) {
        $response = $next($request, $response);
    } else {
        return $response->withJson(array('error' => 'AUTH_USER'), 403);
    }
    return $response;
};

// Check HTTP Basic Authentication
$app->get('/auth', function ($request, $response, $args) {
    $auth_username = $_SERVER['PHP_AUTH_USER'];
    // Return
    return $response->withJson(array(
        'username' => $auth_username,
        'status' => 'OK'
    ), 200);
})->add($check_auth_user);

You're right. PHP is case sensitive.

basic - ❌ PHP_AUTH_USER:

curl 'http://localhost:8080/bla' -H 'Authorization: basic bmlsczp0ZXN0MTIzNA==' | jq
{
  "server": {
    "DOCUMENT_ROOT": "/Users/nils/Projekte/slim/livetracking/public",
    "REMOTE_ADDR": "127.0.0.1",
    "REMOTE_PORT": "52606",
    "SERVER_SOFTWARE": "PHP 5.6.28 Development Server",
    "SERVER_PROTOCOL": "HTTP/1.1",
    "SERVER_NAME": "127.0.0.1",
    "SERVER_PORT": "8080",
    "REQUEST_URI": "/bla",
    "REQUEST_METHOD": "GET",
    "SCRIPT_NAME": "/index.php",
    "SCRIPT_FILENAME": "/Users/nils/Projekte/slim/livetracking/public/index.php",
    "PATH_INFO": "/bla",
    "PHP_SELF": "/index.php/bla",
    "HTTP_HOST": "localhost:8080",
    "HTTP_USER_AGENT": "curl/7.52.1",
    "HTTP_ACCEPT": "*/*",
    "HTTP_AUTHORIZATION": "basic bmlsczp0ZXN0MTIzNA==",
    "REQUEST_TIME_FLOAT": 1488274696.9322,
    "REQUEST_TIME": 1488274696
  }
}

Basic - ✅ PHP_AUTH_USER:

curl 'http://localhost:8080/bla' -H 'Authorization: Basic bmlsczp0ZXN0MTIzNA==' | jq
{
  "server": {
    "DOCUMENT_ROOT": "/Users/nils/Projekte/slim/livetracking/public",
    "REMOTE_ADDR": "127.0.0.1",
    "REMOTE_PORT": "52608",
    "SERVER_SOFTWARE": "PHP 5.6.28 Development Server",
    "SERVER_PROTOCOL": "HTTP/1.1",
    "SERVER_NAME": "127.0.0.1",
    "SERVER_PORT": "8080",
    "REQUEST_URI": "/bla",
    "REQUEST_METHOD": "GET",
    "SCRIPT_NAME": "/index.php",
    "SCRIPT_FILENAME": "/Users/nils/Projekte/slim/livetracking/public/index.php",
    "PATH_INFO": "/bla",
    "PHP_SELF": "/index.php/bla",
    "HTTP_HOST": "localhost:8080",
    "HTTP_USER_AGENT": "curl/7.52.1",
    "HTTP_ACCEPT": "*/*",
    "HTTP_AUTHORIZATION": "Basic bmlsczp0ZXN0MTIzNA==",
   "PHP_AUTH_USER": "nils",
    "PHP_AUTH_PW": "test1234",
    "REQUEST_TIME_FLOAT": 1488274708.5842,
    "REQUEST_TIME": 1488274708
  }
}

If the standard says that basic is OK then the error is in PHP.

tuupola commented 7 years ago

You could try to set PHP_AUTH_USER in the callback.

$app->add(new \Slim\Middleware\HttpBasicAuthentication([
       ...
    "callback" => function ($request, $response, $arguments) {
        $_SERVER['PHP_AUTH_USER'] = $arguments['username'];
    }
]));

That said middleware could actually do that by default if authentication succeeds and PHP_AUTH_USER is still null.

Cyclenerd commented 7 years ago

Good idea. This works for me. Thank you very much.