├── Documentation └── configuration.png ├── Resources └── Public │ └── Icons │ └── Extension.gif ├── Readme.md ├── Configuration ├── RequestMiddlewares.php └── SiteConfiguration │ └── Overrides │ └── basicauth.php ├── ext_emconf.php ├── composer.json └── Classes └── Middleware └── BasicAuth.php /Documentation/configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christophlehmann/httpbasicauth/HEAD/Documentation/configuration.png -------------------------------------------------------------------------------- /Resources/Public/Icons/Extension.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christophlehmann/httpbasicauth/HEAD/Resources/Public/Icons/Extension.gif -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # HTTP Basic Authentication for TYPO3 2 | 3 | * Supports TYPO3 9.5+ 4 | 5 | ## Site Configuration 6 | 7 | ![site configuration](https://raw.githubusercontent.com/christophlehmann/httpbasicauth/master/Documentation/configuration.png) 8 | 9 | ## Webserver environment 10 | 11 | You may need to add `SetEnvIf Authorization .+ HTTP_AUTHORIZATION=$0` to your `.htaccess` to make it work, see #6 12 | -------------------------------------------------------------------------------- /Configuration/RequestMiddlewares.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'lemming/httpbasicauth/basic-auth' => [ 6 | 'target' => \Lemming\Httpbasicauth\Middleware\BasicAuth::class, 7 | 'after' => [ 8 | 'typo3/cms-frontend/site', 9 | 'typo3/cms-frontend/backend-user-authentication' 10 | ], 11 | 'before' => [ 12 | 'typo3/cms-frontend/base-redirect-resolver' 13 | ] 14 | ] 15 | ] 16 | ]; -------------------------------------------------------------------------------- /ext_emconf.php: -------------------------------------------------------------------------------- 1 | 'HTTP Basic Auth', 5 | 'description' => 'HTTP Basic Auth via Site Configuration', 6 | 'category' => 'fe', 7 | 'state' => 'stable', 8 | 'clearCacheOnLoad' => 0, 9 | 'author' => 'Christoph Lehmann', 10 | 'author_email' => 'post@christophlehmann.eu', 11 | 'version' => '2.1.0', 12 | 'constraints' => [ 13 | 'depends' => [ 14 | 'typo3' => '9.5.0-13.4.99', 15 | ], 16 | 'conflicts' => [ 17 | ], 18 | 'suggests' => [ 19 | ], 20 | ], 21 | 'autoload' => [ 22 | 'classmap' => ['Classes'], 23 | ] 24 | ]; 25 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "christophlehmann/httpbasicauth", 3 | "description": "HTTP Basic Auth for TYPO3 9.5+", 4 | "type": "typo3-cms-extension", 5 | "license": [ 6 | "GPL-2.0-or-later" 7 | ], 8 | "keywords": [ 9 | "authentication" 10 | ], 11 | "support": { 12 | "issues": "https://github.com/christophlehmann/httpbasicauth" 13 | }, 14 | "authors": [ 15 | { 16 | "name": "Christoph Lehmann", 17 | "email": "post@christophlehmann.eu" 18 | } 19 | ], 20 | "require": { 21 | "typo3/cms-core": "^9.5 || ^10.4 || ^11.5 || ^12.4 || ^13.0" 22 | }, 23 | "extra": { 24 | "typo3/cms": { 25 | "extension-key": "httpbasicauth" 26 | } 27 | }, 28 | "autoload": { 29 | "psr-4": { 30 | "Lemming\\Httpbasicauth\\": "Classes/" 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Configuration/SiteConfiguration/Overrides/basicauth.php: -------------------------------------------------------------------------------- 1 | 'Enable HTTP Basic Authentication', 5 | 'config' => [ 6 | 'type' => 'check', 7 | 'renderType' => 'checkboxLabeledToggle', 8 | 'items' => [ 9 | [ 10 | 0 => '', 11 | 1 => '', 12 | 'label' => '', 13 | 'labelChecked' => 'Enabled', 14 | 'labelUnchecked' => 'Disabled', 15 | ] 16 | ], 17 | ] 18 | ]; 19 | $GLOBALS['SiteConfiguration']['site']['columns']['basicauth_credentials'] = [ 20 | 'label' => 'Credentials', 21 | 'description' => 'One user:password combination per line', 22 | 'config' => [ 23 | 'type' => 'text', 24 | 'placeholder' => 'user:password' . LF . 'user2:password2', 25 | 'rows' => 5, 26 | 'cols' => 30, 27 | 'max' => 2000, 28 | ], 29 | ]; 30 | $GLOBALS['SiteConfiguration']['site']['columns']['basicauth_allow_devipmask'] = [ 31 | 'label' => 'Grant access when devIPMask matches', 32 | 'config' => [ 33 | 'type' => 'check', 34 | 'default' => '1', 35 | 'renderType' => 'checkboxLabeledToggle', 36 | 'items' => [ 37 | [ 38 | 0 => '', 39 | 1 => '', 40 | 'label' => '', 41 | 'labelChecked' => 'Enabled', 42 | 'labelUnchecked' => 'Disabled', 43 | ] 44 | ], 45 | ] 46 | ]; 47 | $GLOBALS['SiteConfiguration']['site']['columns']['basicauth_allow_beuser'] = [ 48 | 'label' => 'Grant access when backend user logged in', 49 | 'config' => [ 50 | 'type' => 'check', 51 | 'default' => '1', 52 | 'renderType' => 'checkboxLabeledToggle', 53 | 'items' => [ 54 | [ 55 | 0 => '', 56 | 1 => '', 57 | 'label' => '', 58 | 'labelChecked' => 'Enabled', 59 | 'labelUnchecked' => 'Disabled', 60 | ] 61 | ], 62 | ] 63 | ]; 64 | 65 | $GLOBALS['SiteConfiguration']['site']['types']['0']['showitem'] .= ' 66 | ,--div--;HTTP Basic Auth, basicauth_enabled, basicauth_credentials, basicauth_allow_devipmask, basicauth_allow_beuser 67 | '; 68 | 69 | -------------------------------------------------------------------------------- /Classes/Middleware/BasicAuth.php: -------------------------------------------------------------------------------- 1 | getAttribute('site'); 20 | if (!$site instanceof Site) { 21 | return $handler->handle($request); 22 | } 23 | 24 | if (!$this->isBasicAuthenticationEnabled($site)) { 25 | return $handler->handle($request); 26 | } 27 | 28 | if ($this->isAccessGrantedForDeveloperIps($site) && $this->isVisitorIpMatchingDeveloperIpMask()) { 29 | return $handler->handle($request); 30 | } 31 | 32 | if ($this->isAccessGrantedForBackendUsers($site) && $this->isVisitorABackendUser()) { 33 | return $handler->handle($request); 34 | } 35 | 36 | $authorizationHeaderCredentials = $this->getCredentialsFromAuthorizationHeader($request); 37 | if ($authorizationHeaderCredentials && in_array($authorizationHeaderCredentials, $this->getCredentials($site))) { 38 | return $handler->handle($request); 39 | } 40 | 41 | return new HtmlResponse('Not authorized', 401, ['WWW-Authenticate' => 'Basic realm="Not authorized"']); 42 | } 43 | 44 | protected function isBasicAuthenticationEnabled(Site $site): bool 45 | { 46 | try { 47 | return (bool)$site->getAttribute('basicauth_enabled'); 48 | } catch (\InvalidArgumentException) { 49 | // Attribute does not exist 50 | return false; 51 | } 52 | } 53 | 54 | protected function isAccessGrantedForDeveloperIps(Site $site): bool 55 | { 56 | try { 57 | return (bool)$site->getAttribute('basicauth_allow_devipmask'); 58 | } catch (\InvalidArgumentException) { 59 | // Attribute does not exist 60 | return false; 61 | } 62 | } 63 | 64 | protected function isVisitorIpMatchingDeveloperIpMask(): bool 65 | { 66 | return GeneralUtility::cmpIP( 67 | GeneralUtility::getIndpEnv('REMOTE_ADDR'), 68 | $GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask'] 69 | ); 70 | } 71 | 72 | protected function isAccessGrantedForBackendUsers(Site $site): bool 73 | { 74 | try { 75 | return (bool)$site->getAttribute('basicauth_allow_beuser'); 76 | } catch (\InvalidArgumentException) { 77 | // Attribute does not exist 78 | return false; 79 | } 80 | } 81 | 82 | protected function isVisitorABackendUser(): bool 83 | { 84 | $context = GeneralUtility::makeInstance(Context::class); 85 | return $context->getPropertyFromAspect('backend.user', 'id') > 0; 86 | } 87 | 88 | protected function getCredentialsFromAuthorizationHeader(ServerRequestInterface $request): ?string 89 | { 90 | if (preg_match("/Basic\s+(.*)$/i", $request->getHeaderLine("Authorization"), $matches)) { 91 | $credentials = base64_decode($matches[1]); 92 | $usernamePasswordSeparator = ':'; 93 | if (str_contains($credentials, $usernamePasswordSeparator) 94 | && !str_starts_with($credentials, $usernamePasswordSeparator) 95 | ) { 96 | return $credentials; 97 | } 98 | } 99 | return null; 100 | } 101 | 102 | protected function getCredentials(Site $site): array 103 | { 104 | try { 105 | $credentials = GeneralUtility::trimExplode( 106 | LF, 107 | $site->getAttribute('basicauth_credentials'), 108 | true 109 | ); 110 | return $credentials; 111 | } catch (\InvalidArgumentException) { 112 | // Attribute does not exist 113 | return []; 114 | } 115 | } 116 | } 117 | --------------------------------------------------------------------------------