Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow 'headers' as a key in 'relaxed' config key to allow https forwarding #64

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 30 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,38 +159,62 @@ $app->add(new Tuupola\Middleware\HttpBasicAuthentication([

## Security

Browsers send passwords over the wire basically as cleartext. You should always use HTTPS. If the middleware detects insecure usage over HTTP it will throw `RuntimeException`. This rule is relaxed for localhost. To allow insecure usage you must enable it manually by setting `secure` to `false`.
Basic authentication transmits credentials in clear text. For this reason HTTPS should always be used together with basic authentication. If the middleware detects insecure usage over HTTP it will throw a `RuntimeException` with the following message: `Insecure use of middleware over HTTP denied by configuration`.

By default, localhost is allowed to use HTTP. The security behavior of `HttpBasicAuthentication` can also be configured to allow:

- [a whitelist of domains to connect insecurely](#security-whitelist)
- [forwarding of an HTTPS connection to HTTP](#security-forwarding)
- [all traffic](#security-disabling)

### <a name="security-whitelist"></a>How to configure a whitelist:
You can list hosts to allow access insecurely. For example, to allow HTTP traffic from your development host `dev.example.com`, add the hostname to the `relaxed` config key:

``` php
$app = new Slim\App;

$app->add(new Tuupola\Middleware\HttpBasicAuthentication([
"path" => "/admin",
"secure" => false,
"secure" => true,
"relaxed" => ["localhost", "dev.example.com"],
"users" => [
"root" => "t00r",
"somebody" => "passw0rd"
]
]));
```
### <a name="security-forwarding"></a> Allow HTTPS termination and forwarding
If public traffic terminates SSL on a load balancer or proxy and forwards to the application host insecurely, `HttpBasicAuthentication` can inspect request headers to ensure that the original client request was initiated securely. To enable, add the string `headers` to the `relaxed` config key:

Alternatively you can list your development host to have relaxed security.

``` php
```php
$app = new Slim\App;

$app->add(new Tuupola\Middleware\HttpBasicAuthentication([
"path" => "/admin",
"secure" => true,
"relaxed" => ["localhost", "dev.example.com"],
"relaxed" => ["localhost", "headers"],
"users" => [
"root" => "t00r",
"somebody" => "passw0rd"
]
]));
```

### <a name="security-disabling"></a>
To allow insecure usage by any host, you must enable it manually by setting `secure` to `false`:

``` php
$app = new Slim\App;

$app->add(new Tuupola\Middleware\HttpBasicAuthentication([
"path" => "/admin",
"secure" => false,
"users" => [
"root" => "t00r",
"somebody" => "passw0rd"
]
]));
```
## Custom authentication methods

Sometimes passing users in an array is not enough. To authenticate against custom datasource you can pass a callable as `authenticator` parameter. This can be either a class which implements AuthenticatorInterface or anonymous function. Callable receives an array containing `user` and `password` as argument. In both cases authenticator must return either `true` or `false`.
Expand Down
14 changes: 13 additions & 1 deletion src/HttpBasicAuthentication.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,19 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface

/* HTTP allowed only if secure is false or server is in relaxed array. */
if ("https" !== $scheme && true === $this->options["secure"]) {
if (!in_array($host, $this->options["relaxed"])) {
$allowedHost = in_array($host, $this->options["relaxed"]);

/* if 'headers' is in the 'relaxed' key, then we check for forwarding */
$allowedForward = false;
if (in_array("headers", $this->options["relaxed"])) {
if ($request->getHeaderLine("X-Forwarded-Proto") === "https"
&& $request->getHeaderLine('X-Forwarded-Port') === "443"
) {
$allowedForward = true;
}
}

if (!($allowedHost || $allowedForward)) {
$message = sprintf(
"Insecure use of middleware over %s denied by configuration.",
strtoupper($scheme)
Expand Down
29 changes: 29 additions & 0 deletions tests/BasicAuthenticationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,35 @@ public function testShouldRelaxInsecureViaSetting()
$this->assertEquals(401, $response->getStatusCode());
}

public function testShouldRelaxForwardedViaSetting()
{
$request = (new ServerRequestFactory)
->createServerRequest("GET", "http://example.com/api")
->withHeader("X-Forwarded-Proto", "https")
->withHeader("X-Forwarded-Port", "443");

$response = (new ResponseFactory)->createResponse();

$auth = new HttpBasicAuthentication([
"secure" => true,
"relaxed" => ["localhost", "headers"],
"path" => "/api",
"users" => [
"root" => "t00r",
"user" => "passw0rd"
]
]);

$next = function (ServerRequestInterface $request, ResponseInterface $response) {
$response->getBody()->write("Success");
return $response;
};

$response = $auth($request, $response, $next);

$this->assertEquals(401, $response->getStatusCode());
}

public function testShouldBeImmutable()
{
$auth = new HttpBasicAuthentication([
Expand Down