├── 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 | [![Latest Version](https://img.shields.io/packagist/v/intervention/httpauth.svg)](https://packagist.org/packages/intervention/httpauth) 6 | [![Tests](https://github.com/Intervention/httpauth/actions/workflows/build.yml/badge.svg)](https://github.com/Intervention/httpauth/actions/workflows/build.yml) 7 | [![Monthly Downloads](https://img.shields.io/packagist/dm/intervention/httpauth.svg)](https://packagist.org/packages/intervention/httpauth/stats) 8 | [![Support me on Ko-fi](https://raw.githubusercontent.com/Intervention/httpauth/main/.github/images/support.svg)](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 | --------------------------------------------------------------------------------