├── LICENSE
├── README.md
├── composer.json
├── phpunit.xml.dist
└── src
├── Authenticator.php
├── Directive.php
├── Environment.php
├── Exceptions
├── AuthentificationException.php
└── NotSupportedException.php
├── Interfaces
├── DirectiveInterface.php
├── EnvironmentInterface.php
├── TokenInterface.php
└── VaultInterface.php
├── Tokens
├── AbstractToken.php
├── HttpAuthentification.php
├── HttpAuthorization.php
├── PhpAuthDigest.php
├── PhpAuthUser.php
└── RedirectHttpAuthorization.php
├── Type.php
└── Vaults
├── AbstractVault.php
├── BasicVault.php
└── DigestVault.php
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013-present Oliver Vogel
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9 | of the Software, and to permit persons to whom the Software is furnished to do
10 | so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Intervention HttpAuth
2 |
3 | HTTP Authentication Management
4 |
5 | [](https://packagist.org/packages/intervention/httpauth)
6 | [](https://github.com/Intervention/httpauth/actions/workflows/build.yml)
7 | [](https://packagist.org/packages/intervention/httpauth/stats)
8 | [](https://ko-fi.com/interventionphp)
9 |
10 | ## Installation
11 |
12 | You can easily install this library using [Composer](https://getcomposer.org).
13 | Just request the package with the following command:
14 |
15 | ```bash
16 | composer require intervention/httpauth
17 | ```
18 |
19 | ## Documentation
20 |
21 | Read the full [documentation](https://httpauth.intervention.io) for this library.
22 |
23 | ## Usage
24 |
25 | The workflow is easy. Just create an instance of `Authenticator::class` in the first step
26 | and secure your resource in the second step.
27 |
28 | ### 1. Create Authenticator Instance
29 |
30 | To create authenticator instances you can choose between different methods.
31 |
32 | #### Create Instance by Using Static Factory Method
33 |
34 | ```php
35 | use Intervention\HttpAuth\Authenticator;
36 |
37 | // create http basic auth
38 | $auth = Authenticator::basic(
39 | 'myUsername',
40 | 'myPassword',
41 | 'Secured Area',
42 | );
43 |
44 | // create http digest auth
45 | $auth = Authenticator::digest(
46 | 'myUsername',
47 | 'myPassword',
48 | 'Secured Area',
49 | );
50 | ```
51 |
52 | #### Create Instance by Using Class Constructor
53 |
54 | ```php
55 | use Intervention\HttpAuth\Authenticator;
56 |
57 | // alternatively choose DigestVault::class
58 | $vault = new BasicVault(
59 | 'myUsername',
60 | 'myPassword',
61 | 'Secured Area',
62 | );
63 |
64 | $auth = new Authenticator($vault);
65 | ```
66 |
67 | #### Create Instance by Static Factory Method
68 |
69 | ```php
70 | use Intervention\HttpAuth\Authenticator;
71 |
72 | // alternatively choose DigestVault::class
73 | $vault = new BasicVault(
74 | 'myUsername',
75 | 'myPassword',
76 | 'Secured Area',
77 | );
78 |
79 | $auth = Authenticator::withVault($vault);
80 | ```
81 |
82 | ### 2. Ask User for Credentials
83 |
84 | After you created a HTTP authentication instance, you have to call `secure()`
85 | to secure the resource. This results in a 401 HTTP response and the browser
86 | asking for credentials.
87 |
88 | ```php
89 | $auth->secure();
90 | ```
91 |
92 | A character string can optionally be passed to the method. This is displayed if
93 | authentication fails. Output from template engines can also be used here.
94 |
95 | ```php
96 | $auth->secure('Sorry, you can not access this resource!');
97 | ```
98 |
99 | ## Server Configuration
100 |
101 | ### Apache
102 |
103 | If you are using Apache and running PHP with CGI/FastCGI, check the server
104 | configuration to make sure the authorization headers are passed correctly to PHP:
105 |
106 | https://support.deskpro.com/en/kb/articles/missing-authorization-headers-with-apache
107 |
108 | ## Authors
109 |
110 | This library is developed and maintained by [Oliver Vogel](https://intervention.io)
111 |
112 | Thanks to the community of [contributors](https://github.com/Intervention/httpauth/graphs/contributors) who have helped to improve this project.
113 |
114 | ## License
115 |
116 | Intervention HttpAuth is licensed under the [MIT License](LICENSE).
117 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "intervention/httpauth",
3 | "description": "HTTP Authentication Management for PHP",
4 | "homepage": "https://httpauth.intervention.io",
5 | "keywords": [
6 | "authentication",
7 | "http"
8 | ],
9 | "license": "MIT",
10 | "authors": [
11 | {
12 | "name": "Oliver Vogel",
13 | "email": "oliver@intervention.io",
14 | "homepage": "https://intervention.io/"
15 | }
16 | ],
17 | "require": {
18 | "php": "^8.2"
19 | },
20 | "require-dev": {
21 | "phpunit/phpunit": "^10.0 || ^11.0 || ^12.0",
22 | "phpstan/phpstan": "^2.1",
23 | "squizlabs/php_codesniffer": "^3.8",
24 | "slevomat/coding-standard": "~8.0"
25 | },
26 | "autoload": {
27 | "psr-4": {
28 | "Intervention\\HttpAuth\\": "src"
29 | }
30 | },
31 | "autoload-dev": {
32 | "psr-4": {
33 | "Intervention\\HttpAuth\\Tests\\": "tests"
34 | }
35 | },
36 | "config": {
37 | "allow-plugins": {
38 | "dealerdirect/phpcodesniffer-composer-installer": true
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ./tests/Unit/
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/Authenticator.php:
--------------------------------------------------------------------------------
1 | vault->secure($message);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Directive.php:
--------------------------------------------------------------------------------
1 | $parameters
15 | * @return void
16 | */
17 | public function __construct(protected array $parameters = [])
18 | {
19 | }
20 |
21 | /**
22 | * {@inheritdoc}
23 | *
24 | * @see DirectiveInterface::__toString()
25 | */
26 | public function __toString(): string
27 | {
28 | return implode(', ', array_map(
29 | fn(mixed $key, mixed $value): string => sprintf('%s="%s"', $key, $value),
30 | array_keys($this->parameters),
31 | $this->parameters,
32 | ));
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Environment.php:
--------------------------------------------------------------------------------
1 |
17 | */
18 | protected static array $tokenClassnames = [
19 | Tokens\PhpAuthUser::class,
20 | Tokens\HttpAuthentification::class,
21 | Tokens\RedirectHttpAuthorization::class,
22 | Tokens\PhpAuthDigest::class,
23 | Tokens\HttpAuthorization::class,
24 | ];
25 |
26 | /**
27 | * {@inheritdoc}
28 | *
29 | * @see EnvironmentInterface::token()
30 | */
31 | public static function token(): TokenInterface
32 | {
33 | foreach (static::$tokenClassnames as $classname) {
34 | try {
35 | return new $classname();
36 | } catch (AuthentificationException) {
37 | // try next
38 | }
39 | }
40 |
41 | throw new AuthentificationException('Unable to parse authentication token.');
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Exceptions/AuthentificationException.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | public function parse(): array;
15 |
16 | /**
17 | * Access username of token
18 | */
19 | public function username(): ?string;
20 |
21 | /**
22 | * Access password of token
23 | */
24 | public function password(): ?string;
25 |
26 | /**
27 | * Access cnonce of token
28 | */
29 | public function cnonce(): ?string;
30 |
31 | /**
32 | * Access nc of token
33 | */
34 | public function nc(): ?string;
35 |
36 | /**
37 | * Access nonce of token
38 | */
39 | public function nonce(): ?string;
40 |
41 | /**
42 | * Access qop of token
43 | */
44 | public function qop(): ?string;
45 |
46 | /**
47 | * Access response of token
48 | */
49 | public function response(): ?string;
50 |
51 | /**
52 | * Access uri of token
53 | */
54 | public function uri(): ?string;
55 |
56 | /**
57 | * Access realm of token
58 | */
59 | public function realm(): ?string;
60 |
61 | /**
62 | * Access opaque of token
63 | */
64 | public function opaque(): ?string;
65 | }
66 |
--------------------------------------------------------------------------------
/src/Interfaces/VaultInterface.php:
--------------------------------------------------------------------------------
1 |
15 | */
16 | protected array $properties = [];
17 |
18 | /**
19 | * Create new token
20 | *
21 | * @return void
22 | */
23 | public function __construct()
24 | {
25 | $this->properties = $this->parse();
26 | }
27 |
28 | /**
29 | * {@inheritdoc}
30 | *
31 | * @see TokenInterface::username()
32 | */
33 | public function username(): ?string
34 | {
35 | return $this->getArrayValue($this->properties, 'username');
36 | }
37 |
38 | /**
39 | * {@inheritdoc}
40 | *
41 | * @see TokenInterface::password()
42 | */
43 | public function password(): ?string
44 | {
45 | return $this->getArrayValue($this->properties, 'password');
46 | }
47 |
48 | /**
49 | * {@inheritdoc}
50 | *
51 | * @see TokenInterface::cnonce()
52 | */
53 | public function cnonce(): ?string
54 | {
55 | return $this->getArrayValue($this->properties, 'cnonce');
56 | }
57 |
58 | /**
59 | * {@inheritdoc}
60 | *
61 | * @see TokenInterface::nc()
62 | */
63 | public function nc(): ?string
64 | {
65 | return $this->getArrayValue($this->properties, 'nc');
66 | }
67 |
68 | /**
69 | * {@inheritdoc}
70 | *
71 | * @see TokenInterface::nonce()
72 | */
73 | public function nonce(): ?string
74 | {
75 | return $this->getArrayValue($this->properties, 'nonce');
76 | }
77 |
78 | /**
79 | * {@inheritdoc}
80 | *
81 | * @see TokenInterface::qop()
82 | */
83 | public function qop(): ?string
84 | {
85 | return $this->getArrayValue($this->properties, 'qop');
86 | }
87 |
88 | /**
89 | * {@inheritdoc}
90 | *
91 | * @see TokenInterface::response()
92 | */
93 | public function response(): ?string
94 | {
95 | return $this->getArrayValue($this->properties, 'response');
96 | }
97 |
98 | /**
99 | * {@inheritdoc}
100 | *
101 | * @see TokenInterface::uri()
102 | */
103 | public function uri(): ?string
104 | {
105 | return $this->getArrayValue($this->properties, 'uri');
106 | }
107 |
108 | /**
109 | * {@inheritdoc}
110 | *
111 | * @see TokenInterface::realm()
112 | */
113 | public function realm(): ?string
114 | {
115 | return $this->getArrayValue($this->properties, 'realm');
116 | }
117 |
118 | /**
119 | * {@inheritdoc}
120 | *
121 | * @see TokenInterface::opaque()
122 | */
123 | public function opaque(): ?string
124 | {
125 | return $this->getArrayValue($this->properties, 'opaque');
126 | }
127 |
128 | /**
129 | * Return the value of given key in given array data.
130 | * Returns null if key doesn't exists
131 | *
132 | * @param array $data
133 | */
134 | protected function getArrayValue(array $data, mixed $key): mixed
135 | {
136 | if (array_key_exists($key, $data)) {
137 | return $data[$key];
138 | }
139 |
140 | return null;
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/src/Tokens/HttpAuthentification.php:
--------------------------------------------------------------------------------
1 | getArrayValue($_SERVER, 'HTTP_AUTHENTICATION');
20 |
21 | if (is_null($value)) {
22 | throw new AuthentificationException('Failed to parse token.');
23 | }
24 |
25 | if (strtolower(substr((string) $value, 0, 5)) !== 'basic') {
26 | throw new AuthentificationException('Failed to parse token.');
27 | }
28 |
29 | $data = explode(':', base64_decode(substr((string) $value, 6)));
30 |
31 | return [
32 | 'username' => $this->getArrayValue($data, 0),
33 | 'password' => $this->getArrayValue($data, 1),
34 | ];
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Tokens/HttpAuthorization.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | public function parse(): array
18 | {
19 | $value = $this->getArrayValue($_SERVER, 'HTTP_AUTHORIZATION');
20 |
21 | if (is_null($value)) {
22 | throw new AuthentificationException('Failed to parse token.');
23 | }
24 |
25 | if (strtolower(substr((string) $value, 0, 6)) !== 'digest') {
26 | throw new AuthentificationException('Failed to parse token.');
27 | }
28 |
29 | preg_match_all('@(\w+)=(?:(?:")([^"]+)"|([^\s,$]+))@', (string) $value, $matches, PREG_SET_ORDER);
30 |
31 | $properties = [];
32 | foreach ($matches as $m) {
33 | $key = $m[1];
34 | $value = $m[2] ?: $m[3];
35 | $properties[$key] = $value;
36 | }
37 |
38 | return $properties;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Tokens/PhpAuthDigest.php:
--------------------------------------------------------------------------------
1 |
16 | */
17 | public function parse(): array
18 | {
19 | $value = $this->getArrayValue($_SERVER, 'PHP_AUTH_DIGEST');
20 |
21 | if (empty($value)) {
22 | throw new AuthentificationException('Failed to parse token.');
23 | }
24 |
25 | preg_match_all('@(\w+)=(?:(?:")([^"]+)"|([^\s,$]+))@', (string) $value, $matches, PREG_SET_ORDER);
26 |
27 | $properties = [];
28 | foreach ($matches as $m) {
29 | $key = $m[1];
30 | $value = $m[2] ?: $m[3];
31 | $properties[$key] = $value;
32 | }
33 |
34 | return $properties;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Tokens/PhpAuthUser.php:
--------------------------------------------------------------------------------
1 | getArrayValue($_SERVER, 'PHP_AUTH_USER');
20 | $password = $this->getArrayValue($_SERVER, 'PHP_AUTH_PW');
21 |
22 | if (empty($username) || empty($password)) {
23 | throw new AuthentificationException('Failed to parse token.');
24 | }
25 |
26 | return [
27 | 'username' => $username,
28 | 'password' => $password,
29 | ];
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Tokens/RedirectHttpAuthorization.php:
--------------------------------------------------------------------------------
1 | getArrayValue($_SERVER, 'REDIRECT_HTTP_AUTHORIZATION');
20 |
21 | if (is_null($value)) {
22 | throw new AuthentificationException('Failed to parse token.');
23 | }
24 |
25 | if (strtolower(substr((string) $value, 0, 5)) !== 'basic') {
26 | throw new AuthentificationException('Failed to parse token.');
27 | }
28 |
29 | [$username, $password] = explode(':', base64_decode(substr((string) $value, 6)));
30 |
31 | return [
32 | 'username' => $username,
33 | 'password' => $password,
34 | ];
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Type.php:
--------------------------------------------------------------------------------
1 | verify(Environment::token());
44 | } catch (AuthentificationException) {
45 | $this->denyAccess($message);
46 | }
47 | }
48 |
49 | /**
50 | * Set name of realm
51 | */
52 | public function setRealm(string $realm): self
53 | {
54 | $this->realm = $realm;
55 |
56 | return $this;
57 | }
58 |
59 | /**
60 | * Return current realm name
61 | */
62 | public function realm(): string
63 | {
64 | return $this->realm;
65 | }
66 |
67 | /**
68 | * Set username for current vault
69 | */
70 | public function setUsername(string $username): self
71 | {
72 | $this->username = $username;
73 |
74 | return $this;
75 | }
76 |
77 | /**
78 | * Return current username
79 | */
80 | public function username(): string
81 | {
82 | return $this->username;
83 | }
84 |
85 | /**
86 | * Set password for current vault
87 | */
88 | public function setPassword(string $password): self
89 | {
90 | $this->password = $password;
91 |
92 | return $this;
93 | }
94 |
95 | /**
96 | * Return current password
97 | */
98 | public function password(): string
99 | {
100 | return $this->password;
101 | }
102 |
103 | /**
104 | * Set username and password at once
105 | */
106 | public function setCredentials(string $username, string $password): self
107 | {
108 | return $this->setUsername($username)->setPassword($password);
109 | }
110 |
111 | /**
112 | * Send HTTP 401 Header
113 | */
114 | protected function denyAccess(?string $message = null): void
115 | {
116 | $protocol = $_SERVER['SERVER_PROTOCOL'] ?: 'HTTP/1.1';
117 | $message = empty($message) ? '' . $protocol . ' 401 Unauthorized' : $message;
118 |
119 | header($protocol . ' 401 Unauthorized');
120 | header('WWW-Authenticate: ' . $this->type()->value . ' ' . $this->directive());
121 | exit($message);
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/Vaults/BasicVault.php:
--------------------------------------------------------------------------------
1 | username() !== $this->username()) {
23 | throw new AuthentificationException();
24 | }
25 |
26 | if ($token->password() !== $this->password()) {
27 | throw new AuthentificationException();
28 | }
29 | }
30 |
31 | /**
32 | * {@inheritdoc}
33 | *
34 | * @see VaultInterface::type()
35 | */
36 | public function type(): Type
37 | {
38 | return Type::BASIC;
39 | }
40 |
41 | /**
42 | * Return auth directive
43 | *
44 | * @return Directive
45 | */
46 | public function directive(): DirectiveInterface
47 | {
48 | return new Directive([
49 | 'realm' => $this->realm(),
50 | 'charset' => 'UTF-8',
51 | ]);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/Vaults/DigestVault.php:
--------------------------------------------------------------------------------
1 | username() !== $this->username()) {
23 | throw new AuthentificationException();
24 | }
25 |
26 | if ($token->response() !== $this->tokenHash($token)) {
27 | throw new AuthentificationException();
28 | }
29 | }
30 |
31 | /**
32 | * {@inheritdoc}
33 | *
34 | * @see VaultInterface::type()
35 | */
36 | public function type(): Type
37 | {
38 | return Type::DIGEST;
39 | }
40 |
41 | /**
42 | * {@inheritdoc}
43 | *
44 | * @see VaultInterface::directive()
45 | */
46 | public function directive(): DirectiveInterface
47 | {
48 | return new Directive([
49 | 'realm' => $this->realm(),
50 | 'qop' => 'auth',
51 | 'nonce' => uniqid(),
52 | 'opaque' => md5($this->realm()),
53 | ]);
54 | }
55 |
56 | /**
57 | * Build and return hash from given token
58 | */
59 | private function tokenHash(TokenInterface $token): string
60 | {
61 | $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'GET';
62 |
63 | return md5(implode(':', [
64 | md5(sprintf('%s:%s:%s', $token->username(), $this->realm(), $this->password())),
65 | $token->nonce(),
66 | $token->nc(),
67 | $token->cnonce(),
68 | $token->qop(),
69 | md5(sprintf('%s:%s', $method, $token->uri())),
70 | ]));
71 | }
72 | }
73 |
--------------------------------------------------------------------------------